1 /* NetHack 3.7 botl.c $NHDT-Date: 1720397739 2024/07/08 00:15:39 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.264 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Michael Allison, 2006. */
4 /* NetHack may be freely redistributed. See license for details. */
11 extern const char *const hu_stat
[]; /* defined in eat.c */
13 /* also used in insight.c */
14 const char *const enc_stat
[] = {
15 "", "Burdened", "Stressed",
16 "Strained", "Overtaxed", "Overloaded"
19 staticfn
const char *rank(void);
20 staticfn
void bot_via_windowport(void);
21 staticfn
void stat_update_time(void);
22 staticfn
char *get_strength_str(void);
25 get_strength_str(void)
28 int st
= ACURR(A_STR
);
32 Sprintf(buf
, "%2d", st
- 100);
33 else if (st
< STR18(100))
34 Sprintf(buf
, "18/%02d", st
- 18);
36 Sprintf(buf
, "18/**");
38 Sprintf(buf
, "%-1d", st
);
44 check_gold_symbol(void)
46 nhsym goldch
= gs
.showsyms
[COIN_CLASS
+ SYM_OFF_O
];
48 iflags
.invis_goldsym
= (goldch
<= (nhsym
) ' ');
54 static char newbot1
[BUFSZ
];
58 if (suppress_map_output())
59 return strcpy(newbot1
, "");
61 Strcpy(newbot1
, svp
.plname
);
62 if ('a' <= newbot1
[0] && newbot1
[0] <= 'z')
63 newbot1
[0] += 'A' - 'a';
65 Sprintf(nb
= eos(newbot1
), " the ");
71 Strcpy(mbot
, pmname(&mons
[u
.umonnum
], Ugender
));
72 while (mbot
[k
] != 0) {
73 if ((k
== 0 || (k
> 0 && mbot
[k
- 1] == ' ')) && 'a' <= mbot
[k
]
78 Strcpy(nb
= eos(nb
), mbot
);
80 Strcpy(nb
= eos(nb
), rank());
83 Sprintf(nb
= eos(nb
), " ");
85 j
= (int) ((nb
+ 2) - newbot1
); /* strlen(newbot1) but less computation */
87 Sprintf(nb
= eos(nb
), "%*s", i
- j
, " "); /* pad with spaces */
89 Sprintf(nb
= eos(nb
), "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d",
91 ACURR(A_DEX
), ACURR(A_CON
), ACURR(A_INT
), ACURR(A_WIS
),
93 Sprintf(nb
= eos(nb
), "%s",
94 (u
.ualign
.type
== A_CHAOTIC
) ? " Chaotic"
95 : (u
.ualign
.type
== A_NEUTRAL
) ? " Neutral"
99 Sprintf(nb
= eos(nb
), " S:%ld", botl_score());
107 static char newbot2
[BUFSZ
], /* MAXCO: botl.h */
108 /* dungeon location (and gold), hero health (HP, PW, AC),
109 experience (HD if poly'd, else Exp level and maybe Exp points),
110 time (in moves), varying number of status conditions */
111 dloc
[QBUFSZ
], hlth
[QBUFSZ
], expr
[QBUFSZ
],
112 tmmv
[QBUFSZ
], cond
[QBUFSZ
], vers
[QBUFSZ
];
114 size_t dln
, dx
, hln
, xln
, tln
, cln
, vrn
;
118 if (suppress_map_output())
119 return strcpy(newbot2
, "");
122 * Various min(x,9999)'s are to avoid having excessive values
123 * violate the field width assumptions in botl.h and should not
124 * impact normal play. Particularly 64-bit long for gold which
125 * could require many more digits if someone figures out a way
126 * to get and carry a really large (or negative) amount of it.
127 * Turn counter is also long, but we'll risk that.
130 /* dungeon location plus gold */
131 (void) describe_level(dloc
, 1); /* includes at least one trailing space */
132 if ((money
= money_cnt(gi
.invent
)) < 0L)
133 money
= 0L; /* ought to issue impossible() and then discard gold */
134 Sprintf(eos(dloc
), "%s:%-2ld", /* strongest hero can lift ~300000 gold */
135 (iflags
.in_dumplog
|| iflags
.invis_goldsym
) ? "$"
136 : encglyph(objnum_to_glyph(GOLD_PIECE
)),
137 min(money
, 999999L));
139 /* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */
140 dx
= strstri(dloc
, "\\G") ? 9 : 0;
142 /* health and armor class (has trailing space for AC 0..9) */
143 hp
= Upolyd
? u
.mh
: u
.uhp
;
144 hpmax
= Upolyd
? u
.mhmax
: u
.uhpmax
;
147 Sprintf(hlth
, "HP:%d(%d) Pw:%d(%d) AC:%-2d",
148 min(hp
, 9999), min(hpmax
, 9999),
149 min(u
.uen
, 9999), min(u
.uenmax
, 9999), u
.uac
);
154 Sprintf(expr
, "HD:%d", mons
[u
.umonnum
].mlevel
);
155 else if (flags
.showexp
)
156 Sprintf(expr
, "Xp:%d/%-1ld", u
.ulevel
, u
.uexp
);
158 Sprintf(expr
, "Xp:%d", u
.ulevel
);
161 /* time/move counter */
163 Sprintf(tmmv
, "T:%ld", svm
.moves
);
168 /* status conditions; worst ones first */
169 cond
[0] = '\0'; /* once non-empty, cond will have a leading space */
172 * Stoned, Slimed, Strangled, and both types of Sick are all fatal
173 * unless remedied before timeout expires. Should we order them by
174 * shortest time left? [Probably not worth the effort, since it's
175 * unusual for more than one of them to apply at a time.]
178 Strcpy(nb
= eos(nb
), " Stone");
180 Strcpy(nb
= eos(nb
), " Slime");
182 Strcpy(nb
= eos(nb
), " Strngl");
184 if (u
.usick_type
& SICK_VOMITABLE
)
185 Strcpy(nb
= eos(nb
), " FoodPois");
186 if (u
.usick_type
& SICK_NONVOMITABLE
)
187 Strcpy(nb
= eos(nb
), " TermIll");
189 if (u
.uhs
!= NOT_HUNGRY
)
190 Sprintf(nb
= eos(nb
), " %s", hu_stat
[u
.uhs
]);
191 if ((cap
= near_capacity()) > UNENCUMBERED
)
192 Sprintf(nb
= eos(nb
), " %s", enc_stat
[cap
]);
194 Strcpy(nb
= eos(nb
), " Blind");
196 Strcpy(nb
= eos(nb
), " Deaf");
198 Strcpy(nb
= eos(nb
), " Stun");
200 Strcpy(nb
= eos(nb
), " Conf");
202 Strcpy(nb
= eos(nb
), " Hallu");
203 /* levitation and flying are mutually exclusive; riding is not */
205 Strcpy(nb
= eos(nb
), " Lev");
207 Strcpy(nb
= eos(nb
), " Fly");
209 Strcpy(nb
= eos(nb
), " Ride");
212 /* version on status line, with leading space */
214 (void) status_version(vers
, sizeof vers
, TRUE
);
220 * Put the pieces together. If they all fit, keep the traditional
221 * sequence. Otherwise, move least important parts to the end in
222 * case the interface side of things has to truncate. Note that
223 * dloc[] contains '$' encoded in ten character sequence \GXXXXNNNN
224 * so we want to test its display length rather than buffer length.
226 * We don't have an actual display limit here, so have to go by the
227 * width of the map. Since we're reordering rather than truncating,
228 * wider displays can still show wider status than the map if the
229 * interface supports that.
231 if ((dln
- dx
) + 1 + hln
+ 1 + xln
+ 1 + tln
+ 1 + cln
+ vrn
<= COLNO
) {
232 Snprintf(newbot2
, sizeof newbot2
, "%s %s %s %s %s%s", dloc
, hlth
,
233 expr
, tmmv
, cond
, vers
);
235 if (dln
+ 1 + hln
+ 1 + xln
+ 1 + tln
+ 1 + cln
+ vrn
> MAXCO
) {
236 panic("bot2: second status line exceeds MAXCO (%u > %d)",
237 (unsigned) (dln
+ 1 + hln
+ 1 + xln
+ 1 + tln
+ 1 + cln
240 } else if ((dln
- dx
) + 1 + hln
+ 1 + xln
+ 1 + cln
<= COLNO
) {
241 Snprintf(newbot2
, sizeof newbot2
, "%s %s %s %s %s%s", dloc
, hlth
,
242 expr
, cond
, tmmv
, vers
);
243 } else if ((dln
- dx
) + 1 + hln
+ 1 + cln
<= COLNO
) {
244 Snprintf(newbot2
, sizeof newbot2
, "%s %s %s %s %s%s", dloc
, hlth
,
245 cond
, expr
, tmmv
, vers
);
247 Snprintf(newbot2
, sizeof newbot2
, "%s %s %s %s %s%s", hlth
, cond
,
248 dloc
, expr
, tmmv
, vers
);
250 /* only two or three consecutive spaces available to squeeze out */
261 /* dosave() flags completion by setting u.uhp to -1; suppress_map_output()
262 covers program_state.restoring and is used for status as well as map */
263 if (u
.uhp
!= -1 && gy
.youmonst
.data
264 && iflags
.status_updates
&& !suppress_map_output()) {
265 if (VIA_WINDOWPORT()) {
266 bot_via_windowport();
268 curs(WIN_STATUS
, 1, 0);
269 putstr(WIN_STATUS
, 0, do_statusline1());
270 curs(WIN_STATUS
, 1, 1);
271 putmixed(WIN_STATUS
, 0, do_statusline2());
274 disp
.botl
= disp
.botlx
= disp
.time_botl
= FALSE
;
277 /* special purpose status update: move counter ('time' status) only */
283 /* we're called when disp.time_botl is set and general disp.botl
284 is clear; disp.time_botl gets set whenever svm.moves changes value
285 so there's no benefit in tracking previous value to decide whether
286 to skip update; suppress_map_output() handles program_state.restoring
287 and program_state.done_hup (tty hangup => no further output at all)
288 and we use it for maybe skipping status as well as for the map */
289 if (flags
.time
&& iflags
.status_updates
&& !suppress_map_output()) {
290 if (VIA_WINDOWPORT()) {
293 /* old status display updates everything */
297 disp
.time_botl
= FALSE
;
300 /* convert experience level (1..30) to rank index (0..8) */
302 xlev_to_rank(int xlev
)
312 * Conversion is precise but only partially reversible.
314 return (xlev
<= 2) ? 0 : (xlev
<= 30) ? ((xlev
+ 2) / 4) : 8;
317 /* convert rank index (0..8) to experience level (1..30) */
319 rank_to_xlev(int rank
)
329 * We return the low end of each range.
331 return (rank
< 1) ? 1 : (rank
< 2) ? 3
332 : (rank
< 8) ? ((rank
* 4) - 2) : 30;
336 rank_of(int lev
, short monnum
, boolean female
)
338 const struct Role
*role
;
342 for (role
= roles
; role
->name
.m
; role
++)
343 if (monnum
== role
->mnum
)
349 for (i
= xlev_to_rank((int) lev
); i
>= 0; i
--) {
350 if (female
&& role
->rank
[i
].f
)
351 return role
->rank
[i
].f
;
353 return role
->rank
[i
].m
;
356 /* Try the role name, instead */
357 if (female
&& role
->name
.f
)
359 else if (role
->name
.m
)
364 staticfn
const char *
367 return rank_of(u
.ulevel
, Role_switch
, flags
.female
);
378 /* Loop through each of the roles */
379 for (i
= 0; roles
[i
].name
.m
; i
++) {
380 /* loop through each of the rank titles for role #i */
381 for (j
= 0; j
< 9; j
++) {
382 if (roles
[i
].rank
[j
].m
383 && str_start_is(str
, roles
[i
].rank
[j
].m
, TRUE
)) {
387 *title_length
= Strlen(roles
[i
].rank
[j
].m
);
388 return roles
[i
].mnum
;
390 if (roles
[i
].rank
[j
].f
391 && str_start_is(str
, roles
[i
].rank
[j
].f
, TRUE
)) {
395 *title_length
= Strlen(roles
[i
].rank
[j
].f
);
396 return roles
[i
].mnum
;
411 for (i
= 0; i
< 9; i
++) {
412 if (gu
.urole
.rank
[i
].m
&& (r
= strlen(gu
.urole
.rank
[i
].m
)) > maxr
)
414 if (gu
.urole
.rank
[i
].f
&& (r
= strlen(gu
.urole
.rank
[i
].f
)) > maxr
)
417 gm
.mrank_sz
= (int) maxr
;
425 long deepest
= deepest_lev_reached(FALSE
);
426 long umoney
, depthbonus
;
428 /* hidden_gold(False): only gold in containers whose contents are known */
429 umoney
= money_cnt(gi
.invent
) + hidden_gold(FALSE
);
430 /* don't include initial gold; don't impose penalty if it's all gone */
431 if ((umoney
-= u
.umoney0
) < 0L)
433 depthbonus
= 50 * (deepest
- 1)
434 + (deepest
> 30) ? 10000
435 : (deepest
> 20) ? 1000 * (deepest
- 20)
437 /* neither umoney nor depthbonus can grow unusually big (gold due to
438 weight); u.urexp might */
439 return nowrap_add(u
.urexp
, umoney
+ depthbonus
);
441 #endif /* SCORE_ON_BOTL */
443 /* provide the name of the current level for display by various ports */
446 char *buf
, /* output buffer */
447 int dflgs
) /* 1: append trailing space; 2: include dungeon branch name */
449 boolean addspace
= (dflgs
& 1) != 0, /* (used to be unconditional) */
450 addbranch
= (dflgs
& 2) != 0; /* False: status, True: livelog */
453 if (Is_knox(&u
.uz
)) {
454 Sprintf(buf
, "%s", svd
.dungeons
[u
.uz
.dnum
].dname
);
456 } else if (In_quest(&u
.uz
)) {
457 Sprintf(buf
, "Home %d", dunlev(&u
.uz
));
458 } else if (In_endgame(&u
.uz
)) {
459 /* [3.6.2: this used to be "Astral Plane" or generic "End Game"] */
460 (void) endgamelevelname(buf
, depth(&u
.uz
));
462 (void) strsubst(buf
, "Plane of ", ""); /* just keep <element> */
465 /* ports with more room may expand this one */
467 Sprintf(buf
, "%s:%-2d", /* "Dlvl:n" (grep fodder) */
468 In_tutorial(&u
.uz
) ? "Tutorial" : "Dlvl", depth(&u
.uz
));
470 Sprintf(buf
, "level %d", depth(&u
.uz
));
474 Sprintf(eos(buf
), ", %s", svd
.dungeons
[u
.uz
.dnum
].dname
);
475 (void) strsubst(buf
, "The ", "the ");
482 /* =======================================================================*/
483 /* statusnew routines */
484 /* =======================================================================*/
486 /* structure that tracks the status details in the core */
488 #ifdef STATUS_HILITES
489 #endif /* STATUS_HILITES */
491 staticfn boolean
eval_notify_windowport_field(int, boolean
*, int);
492 staticfn
void evaluate_and_notify_windowport(boolean
*, int);
493 staticfn
void init_blstats(void);
494 staticfn
int compare_blstats(struct istat_s
*, struct istat_s
*);
495 staticfn
char *anything_to_s(char *, anything
*, int);
496 staticfn
int percentage(struct istat_s
*, struct istat_s
*);
497 staticfn
int exp_percentage(void);
498 staticfn
int QSORTCALLBACK
cond_cmp(const genericptr
, const genericptr
);
499 staticfn
int QSORTCALLBACK
menualpha_cmp(const genericptr
, const genericptr
);
501 #ifdef STATUS_HILITES
502 staticfn
void s_to_anything(anything
*, char *, int);
503 staticfn
enum statusfields
fldname_to_bl_indx(const char *);
504 staticfn boolean
hilite_reset_needed(struct istat_s
*, long);
505 staticfn boolean
noneoftheabove(const char *);
506 staticfn
struct hilite_s
*get_hilite(int, int, genericptr_t
, int, int, int *);
507 staticfn
void split_clridx(int, int *, int *);
508 staticfn boolean
is_ltgt_percentnumber(const char *);
509 staticfn boolean
has_ltgt_percentnumber(const char *);
510 staticfn
int splitsubfields(char *, char ***, int);
511 staticfn boolean
is_fld_arrayvalues(const char *, const char *const *,
513 staticfn
int query_arrayvalue(const char *, const char *const *, int, int);
514 staticfn
void status_hilite_add_threshold(int, struct hilite_s
*);
515 staticfn boolean
parse_status_hl2(char (*)[QBUFSZ
], boolean
);
516 staticfn
unsigned long query_conditions(void);
517 staticfn
char *conditionbitmask2str(unsigned long);
518 staticfn
unsigned long match_str2conditionbitmask(const char *);
519 staticfn
unsigned long str2conditionbitmask(char *);
520 staticfn boolean
parse_condition(char (*)[QBUFSZ
], int);
521 staticfn
char *hlattr2attrname(int, char *, size_t);
522 staticfn
void status_hilite_linestr_add(int, struct hilite_s
*, unsigned long,
524 staticfn
void status_hilite_linestr_done(void);
525 staticfn
int status_hilite_linestr_countfield(int);
526 staticfn
void status_hilite_linestr_gather_conditions(void);
527 staticfn
void status_hilite_linestr_gather(void);
528 staticfn
char *status_hilite2str(struct hilite_s
*);
529 staticfn
int status_hilite_menu_choose_field(void);
530 staticfn
int status_hilite_menu_choose_behavior(int);
531 staticfn
int status_hilite_menu_choose_updownboth(int, const char *, boolean
,
533 staticfn boolean
status_hilite_menu_add(int);
534 staticfn boolean
status_hilite_remove(int);
535 staticfn boolean
status_hilite_menu_fld(int);
536 staticfn
void status_hilites_viewall(void);
538 #define has_hilite(i) (gb.blstats[0][(i)].thresholds)
539 /* TH_UPDOWN encompasses specific 'up' and 'down' also general 'changed' */
540 #define Is_Temp_Hilite(rule) ((rule) && (rule)->behavior == BL_TH_UPDOWN)
542 /* pointers to current hilite rule and list of this field's defined rules */
543 #define INIT_THRESH , (struct hilite_s *) 0, (struct hilite_s *) 0
544 #else /* !STATUS_HILITES */
545 #define INIT_THRESH /*empty*/
548 #define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
549 { name, fmtstr, 0L, FALSE, FALSE, 0, anytyp, \
550 { (genericptr_t) 0 }, { (genericptr_t) 0 }, (char *) 0, \
551 wid, -1, fld INIT_THRESH }
552 #define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
553 { name, fmtstr, 0L, FALSE, TRUE, 0, anytyp, \
554 { (genericptr_t) 0 }, { (genericptr_t) 0 }, (char *) 0, \
555 wid, maxfld, fld INIT_THRESH }
557 /* If entries are added to this, botl.h will require updating too.
558 'max' value of BL_EXP gets special handling since the percentage
559 involved isn't a direct 100*current/maximum calculation. */
560 static struct istat_s initblstats
[MAXBLSTATS
] = {
561 INIT_BLSTAT("title", "%s", ANY_STR
, MAXVALWIDTH
, BL_TITLE
),
562 INIT_BLSTAT("strength", " St:%s", ANY_INT
, 10, BL_STR
),
563 INIT_BLSTAT("dexterity", " Dx:%s", ANY_INT
, 10, BL_DX
),
564 INIT_BLSTAT("constitution", " Co:%s", ANY_INT
, 10, BL_CO
),
565 INIT_BLSTAT("intelligence", " In:%s", ANY_INT
, 10, BL_IN
),
566 INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT
, 10, BL_WI
),
567 INIT_BLSTAT("charisma", " Ch:%s", ANY_INT
, 10, BL_CH
),
568 INIT_BLSTAT("alignment", " %s", ANY_STR
, 40, BL_ALIGN
),
569 INIT_BLSTAT("score", " S:%s", ANY_LONG
, 20, BL_SCORE
),
570 INIT_BLSTAT("carrying-capacity", " %s", ANY_INT
, 20, BL_CAP
),
571 INIT_BLSTAT("gold", " %s", ANY_LONG
, 30, BL_GOLD
),
572 INIT_BLSTATP("power", " Pw:%s", ANY_INT
, 10, BL_ENEMAX
, BL_ENE
),
573 INIT_BLSTAT("power-max", "(%s)", ANY_INT
, 10, BL_ENEMAX
),
574 INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT
, 10, BL_EXP
, BL_XP
),
575 INIT_BLSTAT("armor-class", " AC:%s", ANY_INT
, 10, BL_AC
),
576 INIT_BLSTAT("HD", " HD:%s", ANY_INT
, 10, BL_HD
),
577 INIT_BLSTAT("time", " T:%s", ANY_LONG
, 20, BL_TIME
),
578 /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */
579 INIT_BLSTAT("hunger", " %s", ANY_INT
, 40, BL_HUNGER
),
580 INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT
, 10, BL_HPMAX
, BL_HP
),
581 INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT
, 10, BL_HPMAX
),
582 INIT_BLSTAT("dungeon-level", "%s", ANY_STR
, MAXVALWIDTH
, BL_LEVELDESC
),
583 INIT_BLSTATP("experience", "/%s", ANY_LONG
, 20, BL_EXP
, BL_EXP
),
584 INIT_BLSTAT("condition", "%s", ANY_MASK32
, 0, BL_CONDITION
),
585 /* optional; once set it doesn't change unless 'showvers' option is
586 toggled or player modifies the 'versinfo' option;
587 available mostly for screenshots or someone looking over shoulder;
588 blstat[][BL_VERS] is actually an int copy of flags.versinfo (0...7) */
589 INIT_BLSTAT("version", " %s", ANY_STR
, MAXVALWIDTH
, BL_VERS
),
596 #ifdef STATUS_HILITES
598 static const struct condmap condition_aliases
[] = {
599 { "strangled", BL_MASK_STRNGL
},
600 { "all", BL_MASK_BAREH
| BL_MASK_BLIND
| BL_MASK_BUSY
601 | BL_MASK_CONF
| BL_MASK_DEAF
| BL_MASK_ELF_IRON
602 | BL_MASK_FLY
| BL_MASK_FOODPOIS
| BL_MASK_GLOWHANDS
603 | BL_MASK_GRAB
| BL_MASK_HALLU
| BL_MASK_HELD
604 | BL_MASK_ICY
| BL_MASK_INLAVA
| BL_MASK_LEV
605 | BL_MASK_PARLYZ
| BL_MASK_RIDE
| BL_MASK_SLEEPING
606 | BL_MASK_SLIME
| BL_MASK_SLIPPERY
| BL_MASK_STONE
607 | BL_MASK_STRNGL
| BL_MASK_STUN
| BL_MASK_SUBMERGED
608 | BL_MASK_TERMILL
| BL_MASK_TETHERED
609 | BL_MASK_TRAPPED
| BL_MASK_UNCONSC
610 | BL_MASK_WOUNDEDL
| BL_MASK_HOLDING
},
611 { "major_troubles", BL_MASK_FOODPOIS
| BL_MASK_GRAB
| BL_MASK_INLAVA
612 | BL_MASK_SLIME
| BL_MASK_STONE
| BL_MASK_STRNGL
614 { "minor_troubles", BL_MASK_BLIND
| BL_MASK_CONF
| BL_MASK_DEAF
615 | BL_MASK_HALLU
| BL_MASK_PARLYZ
| BL_MASK_SUBMERGED
617 { "movement", BL_MASK_LEV
| BL_MASK_FLY
| BL_MASK_RIDE
},
618 { "opt_in", BL_MASK_BAREH
| BL_MASK_BUSY
| BL_MASK_GLOWHANDS
619 | BL_MASK_HELD
| BL_MASK_ICY
| BL_MASK_PARLYZ
620 | BL_MASK_SLEEPING
| BL_MASK_SLIPPERY
621 | BL_MASK_SUBMERGED
| BL_MASK_TETHERED
623 | BL_MASK_UNCONSC
| BL_MASK_WOUNDEDL
627 #endif /* STATUS_HILITES */
629 /* condition names and their abbreviations are used by windowport code */
630 const struct conditions_t conditions
[] = {
631 /* ranking, mask, identifier, txt1, txt2, txt3 */
632 { 20, BL_MASK_BAREH
, bl_bareh
, { "Bare", "Bar", "Bh" } },
633 { 10, BL_MASK_BLIND
, bl_blind
, { "Blind", "Blnd", "Bl" } },
634 { 20, BL_MASK_BUSY
, bl_busy
, { "Busy", "Bsy", "By" } },
635 { 10, BL_MASK_CONF
, bl_conf
, { "Conf", "Cnf", "Cf" } },
636 { 10, BL_MASK_DEAF
, bl_deaf
, { "Deaf", "Def", "Df" } },
637 { 15, BL_MASK_ELF_IRON
, bl_elf_iron
, { "Iron", "Irn", "Fe" } },
638 { 10, BL_MASK_FLY
, bl_fly
, { "Fly", "Fly", "Fl" } },
639 { 6, BL_MASK_FOODPOIS
, bl_foodpois
, { "FoodPois", "Fpois", "Poi" } },
640 { 20, BL_MASK_GLOWHANDS
, bl_glowhands
, { "Glow", "Glo", "Gl" } },
641 { 2, BL_MASK_GRAB
, bl_grab
, { "Grab", "Grb", "Gr" } },
642 { 10, BL_MASK_HALLU
, bl_hallu
, { "Hallu", "Hal", "Hl" } },
643 { 20, BL_MASK_HELD
, bl_held
, { "Held", "Hld", "Hd" } },
644 { 20, BL_MASK_ICY
, bl_icy
, { "Icy", "Icy", "Ic" } },
645 { 8, BL_MASK_INLAVA
, bl_inlava
, { "Lava", "Lav", "La" } },
646 { 10, BL_MASK_LEV
, bl_lev
, { "Lev", "Lev", "Lv" } },
647 { 20, BL_MASK_PARLYZ
, bl_parlyz
, { "Parlyz", "Para", "Par" } },
648 { 10, BL_MASK_RIDE
, bl_ride
, { "Ride", "Rid", "Rd" } },
649 { 20, BL_MASK_SLEEPING
, bl_sleeping
, { "Zzz", "Zzz", "Zz" } },
650 { 6, BL_MASK_SLIME
, bl_slime
, { "Slime", "Slim", "Slm" } },
651 { 20, BL_MASK_SLIPPERY
, bl_slippery
, { "Slip", "Sli", "Sl" } },
652 { 6, BL_MASK_STONE
, bl_stone
, { "Stone", "Ston", "Sto" } },
653 { 4, BL_MASK_STRNGL
, bl_strngl
, { "Strngl", "Stngl", "Str" } },
654 { 10, BL_MASK_STUN
, bl_stun
, { "Stun", "Stun", "St" } },
655 { 15, BL_MASK_SUBMERGED
, bl_submerged
, { "Sub", "Sub", "Sw" } },
656 { 6, BL_MASK_TERMILL
, bl_termill
, { "TermIll", "Ill", "Ill" } },
657 { 20, BL_MASK_TETHERED
, bl_tethered
, { "Teth", "Tth", "Te" } },
658 { 20, BL_MASK_TRAPPED
, bl_trapped
, { "Trap", "Trp", "Tr" } },
659 { 20, BL_MASK_UNCONSC
, bl_unconsc
, { "Out", "Out", "KO" } },
660 { 20, BL_MASK_WOUNDEDL
, bl_woundedl
, { "Legs", "Leg", "Lg" } },
661 { 20, BL_MASK_HOLDING
, bl_holding
, { "UHold", "UHld", "UHd" } },
664 struct condtests_t condtests
[CONDITION_COUNT
] = {
665 /* id, useropt, opt_in or out, enabled, configchoice, testresult;
666 default value for enabled is !opt_in but can get changed via options */
667 { bl_bareh
, "barehanded", opt_in
, FALSE
, FALSE
, FALSE
},
668 { bl_blind
, "blind", opt_out
, TRUE
, FALSE
, FALSE
},
669 { bl_busy
, "busy", opt_in
, FALSE
, FALSE
, FALSE
},
670 { bl_conf
, "conf", opt_out
, TRUE
, FALSE
, FALSE
},
671 { bl_deaf
, "deaf", opt_out
, TRUE
, FALSE
, FALSE
},
672 { bl_elf_iron
, "iron", opt_out
, TRUE
, FALSE
, FALSE
},
673 { bl_fly
, "fly", opt_out
, TRUE
, FALSE
, FALSE
},
674 { bl_foodpois
, "foodPois", opt_out
, TRUE
, FALSE
, FALSE
},
675 { bl_glowhands
, "glowhands", opt_in
, FALSE
, FALSE
, FALSE
},
676 { bl_grab
, "grab", opt_out
, TRUE
, FALSE
, FALSE
},
677 { bl_hallu
, "hallucinat", opt_out
, TRUE
, FALSE
, FALSE
},
678 { bl_held
, "held", opt_in
, FALSE
, FALSE
, FALSE
},
679 { bl_icy
, "ice", opt_in
, FALSE
, FALSE
, FALSE
},
680 { bl_inlava
, "lava", opt_out
, TRUE
, FALSE
, FALSE
},
681 { bl_lev
, "levitate", opt_out
, TRUE
, FALSE
, FALSE
},
682 { bl_parlyz
, "paralyzed", opt_in
, FALSE
, FALSE
, FALSE
},
683 { bl_ride
, "ride", opt_out
, TRUE
, FALSE
, FALSE
},
684 { bl_sleeping
, "sleep", opt_in
, FALSE
, FALSE
, FALSE
},
685 { bl_slime
, "slime", opt_out
, TRUE
, FALSE
, FALSE
},
686 { bl_slippery
, "slip", opt_in
, FALSE
, FALSE
, FALSE
},
687 { bl_stone
, "stone", opt_out
, TRUE
, FALSE
, FALSE
},
688 { bl_strngl
, "strngl", opt_out
, TRUE
, FALSE
, FALSE
},
689 { bl_stun
, "stun", opt_out
, TRUE
, FALSE
, FALSE
},
690 { bl_submerged
, "submerged", opt_in
, FALSE
, FALSE
, FALSE
},
691 { bl_termill
, "termIll", opt_out
, TRUE
, FALSE
, FALSE
},
692 { bl_tethered
, "tethered", opt_in
, FALSE
, FALSE
, FALSE
},
693 { bl_trapped
, "trap", opt_in
, FALSE
, FALSE
, FALSE
},
694 { bl_unconsc
, "unconscious", opt_in
, FALSE
, FALSE
, FALSE
},
695 { bl_woundedl
, "woundedlegs", opt_in
, FALSE
, FALSE
, FALSE
},
696 { bl_holding
, "holding", opt_in
, FALSE
, FALSE
, FALSE
},
698 /* condition indexing */
699 int cond_idx
[CONDITION_COUNT
] = { 0 };
702 static boolean cache_avail
[3] = { FALSE
, FALSE
, FALSE
};
703 static boolean cache_reslt
[3] = { FALSE
, FALSE
, FALSE
};
704 static const char *cache_nomovemsg
= NULL
, *cache_multi_reason
= NULL
;
706 #define cond_cache_prepA() \
708 boolean clear_cache = FALSE, refresh_cache = FALSE; \
710 if (gm.multi < 0) { \
711 if (gn.nomovemsg || gm.multi_reason) { \
712 if (cache_nomovemsg != gn.nomovemsg) \
713 refresh_cache = TRUE; \
714 if (cache_multi_reason != gm.multi_reason) \
715 refresh_cache = TRUE; \
717 clear_cache = TRUE; \
720 clear_cache = TRUE; \
723 cache_nomovemsg = (const char *) 0; \
724 cache_multi_reason = (const char *) 0; \
726 if (refresh_cache) { \
727 cache_nomovemsg = gn.nomovemsg; \
728 cache_multi_reason = gm.multi_reason; \
730 if (clear_cache || refresh_cache) { \
731 cache_reslt[0] = cache_avail[0] = FALSE; \
732 cache_reslt[1] = cache_avail[1] = FALSE; \
736 /* we don't put this next declaration in #ifdef STATUS_HILITES.
737 * In the absence of STATUS_HILITES, each array
738 * element will be 0 however, and quite meaningless,
739 * but we need to pass the first array element as
740 * the final argument of status_update, with or
741 * without STATUS_HILITES.
745 bot_via_windowport(void)
754 panic("bot before init.");
756 /* toggle from previous iteration */
757 idx
= 1 - gn
.now_or_before_idx
; /* 0 -> 1, 1 -> 0 */
758 gn
.now_or_before_idx
= idx
;
760 /* clear the "value set" indicators */
761 (void) memset((genericptr_t
) gv
.valset
, 0, MAXBLSTATS
* sizeof (boolean
));
764 * Note: min(x,9999) - we enforce the same maximum on hp, maxhp,
765 * pw, maxpw, and gold as basic status formatting so that the two
766 * modes of status display don't produce different information.
770 * Player name and title.
772 Strcpy(nb
= buf
, svp
.plname
);
773 nb
[0] = highc(nb
[0]);
774 titl
= !Upolyd
? rank() : pmname(&mons
[u
.umonnum
], Ugender
);
775 i
= (int) (strlen(buf
) + sizeof " the " + strlen(titl
) - sizeof "");
776 /* if "Name the Rank/monster" is too long, we truncate the name
777 but always keep at least 10 characters of it; when hitpointbar is
778 enabled, anything beyond 30 (long monster name) will be truncated */
780 i
= 30 - (int) (sizeof " the " + strlen(titl
) - sizeof "");
781 nb
[max(i
, 10)] = '\0';
783 Strcpy(nb
= eos(nb
), " the ");
784 Strcpy(nb
= eos(nb
), titl
);
785 if (Upolyd
) { /* when poly'd, capitalize monster name */
786 for (i
= 0; nb
[i
]; i
++)
787 if (i
== 0 || nb
[i
- 1] == ' ')
788 nb
[i
] = highc(nb
[i
]);
790 Sprintf(gb
.blstats
[idx
][BL_TITLE
].val
, "%-30s", buf
);
791 gv
.valset
[BL_TITLE
] = TRUE
; /* indicate val already set */
794 gb
.blstats
[idx
][BL_STR
].a
.a_int
= ACURR(A_STR
);
795 Strcpy(gb
.blstats
[idx
][BL_STR
].val
, get_strength_str());
796 gv
.valset
[BL_STR
] = TRUE
; /* indicate val already set */
798 /* Dexterity, constitution, intelligence, wisdom, charisma. */
799 gb
.blstats
[idx
][BL_DX
].a
.a_int
= ACURR(A_DEX
);
800 gb
.blstats
[idx
][BL_CO
].a
.a_int
= ACURR(A_CON
);
801 gb
.blstats
[idx
][BL_IN
].a
.a_int
= ACURR(A_INT
);
802 gb
.blstats
[idx
][BL_WI
].a
.a_int
= ACURR(A_WIS
);
803 gb
.blstats
[idx
][BL_CH
].a
.a_int
= ACURR(A_CHA
);
806 Strcpy(gb
.blstats
[idx
][BL_ALIGN
].val
, (u
.ualign
.type
== A_CHAOTIC
)
808 : (u
.ualign
.type
== A_NEUTRAL
)
813 gb
.blstats
[idx
][BL_SCORE
].a
.a_long
=
815 flags
.showscore
? botl_score() :
820 i
= Upolyd
? u
.mh
: u
.uhp
;
821 if (i
< 0) /* gameover sets u.uhp to -1 */
823 gb
.blstats
[idx
][BL_HP
].rawval
.a_int
= i
;
824 gb
.blstats
[idx
][BL_HP
].a
.a_int
= min(i
, 9999);
825 i
= Upolyd
? u
.mhmax
: u
.uhpmax
;
826 gb
.blstats
[idx
][BL_HPMAX
].rawval
.a_int
= i
;
827 gb
.blstats
[idx
][BL_HPMAX
].a
.a_int
= min(i
, 9999);
830 (void) describe_level(gb
.blstats
[idx
][BL_LEVELDESC
].val
, 1);
831 gv
.valset
[BL_LEVELDESC
] = TRUE
; /* indicate val already set */
834 if ((money
= money_cnt(gi
.invent
)) < 0L)
835 money
= 0L; /* ought to issue impossible() and then discard gold */
836 gb
.blstats
[idx
][BL_GOLD
].rawval
.a_long
= money
;
837 gb
.blstats
[idx
][BL_GOLD
].a
.a_long
= min(money
, 999999L);
839 * The tty port needs to display the current symbol for gold
840 * as a field header, so to accommodate that we pass gold with
841 * that already included. If a window port needs to use the text
842 * gold amount without the leading "$:" the port will have to
843 * skip past ':' to the value pointer it was passed in status_update()
844 * for the BL_GOLD case.
846 * Another quirk of BL_GOLD is that the field display may have
847 * changed if a new symbol set was loaded, or we entered or left
850 * The currency prefix is encoded as ten character \GXXXXNNNN
853 Sprintf(gb
.blstats
[idx
][BL_GOLD
].val
, "%s:%ld",
854 (iflags
.in_dumplog
|| iflags
.invis_goldsym
) ? "$"
855 : encglyph(objnum_to_glyph(GOLD_PIECE
)),
856 gb
.blstats
[idx
][BL_GOLD
].a
.a_long
);
857 gv
.valset
[BL_GOLD
] = TRUE
; /* indicate val already set */
859 /* Power (magical energy) */
860 gb
.blstats
[idx
][BL_ENE
].rawval
.a_int
= u
.uen
;
861 gb
.blstats
[idx
][BL_ENE
].a
.a_int
= min(u
.uen
, 9999);
862 gb
.blstats
[idx
][BL_ENEMAX
].rawval
.a_int
= u
.uenmax
;
863 gb
.blstats
[idx
][BL_ENEMAX
].a
.a_int
= min(u
.uenmax
, 9999);
866 gb
.blstats
[idx
][BL_AC
].a
.a_int
= u
.uac
;
868 /* Monster level (if Upolyd) */
869 gb
.blstats
[idx
][BL_HD
].a
.a_int
= Upolyd
? (int) mons
[u
.umonnum
].mlevel
: 0;
872 gb
.blstats
[idx
][BL_XP
].a
.a_int
= u
.ulevel
;
873 gb
.blstats
[idx
][BL_EXP
].a
.a_long
= u
.uexp
;
876 gb
.blstats
[idx
][BL_TIME
].a
.a_long
= svm
.moves
;
879 /* note: u.uhs is unsigned, and 3.6.1's STATUS_HILITE defined
880 BL_HUNGER to be ANY_UINT, but that was the only non-int/non-long
881 numeric field so it's far simpler to treat it as plain int and
882 not need ANY_UINT handling at all */
883 gb
.blstats
[idx
][BL_HUNGER
].a
.a_int
= (int) u
.uhs
;
884 Strcpy(gb
.blstats
[idx
][BL_HUNGER
].val
,
885 (u
.uhs
!= NOT_HUNGRY
) ? hu_stat
[u
.uhs
] : "");
886 gv
.valset
[BL_HUNGER
] = TRUE
;
888 /* Carrying capacity */
889 cap
= near_capacity();
890 gb
.blstats
[idx
][BL_CAP
].a
.a_int
= cap
;
891 Strcpy(gb
.blstats
[idx
][BL_CAP
].val
,
892 (cap
> UNENCUMBERED
) ? enc_stat
[cap
] : "");
893 gv
.valset
[BL_CAP
] = TRUE
;
895 /* Version; unchanging unless player toggles 'showvers' option or
896 modifies 'versinfo' option; toggling showvers off will clear it */
897 if (gb
.blstats
[idx
][BL_VERS
].a
.a_int
!= (int) flags
.versinfo
) {
898 gb
.blstats
[idx
][BL_VERS
].a
.a_int
= (int) flags
.versinfo
;
899 gv
.valset
[BL_VERS
] = FALSE
;
901 if (!gv
.valset
[BL_VERS
]) {
902 (void) status_version(gb
.blstats
[idx
][BL_VERS
].val
,
903 gb
.blstats
[idx
][BL_VERS
].valwidth
, FALSE
);
904 gv
.valset
[BL_VERS
] = TRUE
;
909 gb
.blstats
[idx
][BL_CONDITION
].a
.a_ulong
= 0L;
912 * Avoid anything that does string comparisons in here because this
913 * is called *extremely* often, for every screen update and the same
914 * string comparisons would be repeated, thus contributing toward
915 * performance degradation. If it is essential that string comparisons
916 * are needed for a particular condition, consider adding a caching
917 * mechanism to limit the string comparisons to the first occurrence
918 * for that cache lifetime. There is caching of that nature done for
919 * unconsc (1) and parlyz (2) because the suggested way of being able
920 * to distinguish unconsc, parlyz, sleeping, and busy involves multiple
921 * string comparisons.
923 * [Rebuttal: it's called a lot for Windows and MS-DOS because their
924 * sample run-time configuration file enables 'time' (move counter).
925 * The optimization to bypass full status update when only 'time'
926 * has changed (via timebot(), only effective for VIA_WINDOWPORT()
927 * configurations) should ameliorate that.]
930 #define test_if_enabled(c) if (condtests[(c)].enabled) condtests[(c)].test
932 condtests
[bl_foodpois
].test
= condtests
[bl_termill
].test
= FALSE
;
934 test_if_enabled(bl_foodpois
) = (u
.usick_type
& SICK_VOMITABLE
) != 0;
935 test_if_enabled(bl_termill
) = (u
.usick_type
& SICK_NONVOMITABLE
) != 0;
937 condtests
[bl_inlava
].test
= condtests
[bl_tethered
].test
938 = condtests
[bl_trapped
].test
= FALSE
;
940 test_if_enabled(bl_inlava
) = (u
.utraptype
== TT_LAVA
);
941 test_if_enabled(bl_tethered
) = (u
.utraptype
== TT_BURIEDBALL
);
942 /* if in-lava or tethered is disabled and the condition applies,
943 lump it in with trapped */
944 test_if_enabled(bl_trapped
) = (!condtests
[bl_inlava
].test
945 && !condtests
[bl_tethered
].test
);
947 condtests
[bl_grab
].test
= condtests
[bl_held
].test
949 = condtests
[bl_engulfed
].test
951 = condtests
[bl_holding
].test
= FALSE
;
953 /* it is possible for a hero in sticks() form to be swallowed,
954 so swallowed needs to be checked first; it is not possible for
955 a hero in sticks() form to be held--sticky hero does the holding
956 even if u.ustuck is also a holder */
958 /* engulfed/swallowed isn't currently a tracked status condition;
959 "held" might look odd for it but seems better than blank */
961 test_if_enabled(bl_engulfed
) = TRUE
;
963 test_if_enabled(bl_held
) = TRUE
;
965 } else if (Upolyd
&& sticks(gy
.youmonst
.data
)) {
966 test_if_enabled(bl_holding
) = TRUE
;
968 /* grab == hero is held by sea monster and about to be drowned;
969 held == hero is held by something else and can't move away */
970 test_if_enabled(bl_grab
) = (u
.ustuck
->data
->mlet
== S_EEL
);
971 test_if_enabled(bl_held
) = !condtests
[bl_grab
].test
;
974 condtests
[bl_blind
].test
= (Blind
) ? TRUE
: FALSE
;
975 condtests
[bl_conf
].test
= (Confusion
) ? TRUE
: FALSE
;
976 condtests
[bl_deaf
].test
= (Deaf
) ? TRUE
: FALSE
;
977 condtests
[bl_fly
].test
= (Flying
) ? TRUE
: FALSE
;
978 condtests
[bl_glowhands
].test
= (u
.umconf
) ? TRUE
: FALSE
;
979 condtests
[bl_hallu
].test
= (Hallucination
) ? TRUE
: FALSE
;
980 condtests
[bl_lev
].test
= (Levitation
) ? TRUE
: FALSE
;
981 condtests
[bl_ride
].test
= (u
.usteed
) ? TRUE
: FALSE
;
982 condtests
[bl_slime
].test
= (Slimed
) ? TRUE
: FALSE
;
983 condtests
[bl_stone
].test
= (Stoned
) ? TRUE
: FALSE
;
984 condtests
[bl_strngl
].test
= (Strangled
) ? TRUE
: FALSE
;
985 condtests
[bl_stun
].test
= (Stunned
) ? TRUE
: FALSE
;
986 condtests
[bl_submerged
].test
= (Underwater
) ? TRUE
: FALSE
;
987 test_if_enabled(bl_elf_iron
) = (FALSE
);
988 test_if_enabled(bl_bareh
) = (!uarmg
&& !uwep
);
989 test_if_enabled(bl_icy
) = (levl
[u
.ux
][u
.uy
].typ
== ICE
);
990 test_if_enabled(bl_slippery
) = (Glib
) ? TRUE
: FALSE
;
991 test_if_enabled(bl_woundedl
) = (Wounded_legs
) ? TRUE
: FALSE
;
995 if (condtests
[bl_unconsc
].enabled
996 && cache_nomovemsg
&& !cache_avail
[0]) {
997 cache_reslt
[0] = (!u
.usleep
&& unconscious());
998 cache_avail
[0] = TRUE
;
1000 if (condtests
[bl_parlyz
].enabled
1001 && cache_multi_reason
&& !cache_avail
[1]) {
1002 cache_reslt
[1] = (!strncmp(cache_multi_reason
, "paralyzed", 9)
1003 || !strncmp(cache_multi_reason
, "frozen", 6));
1004 cache_avail
[1] = TRUE
;
1006 if (cache_avail
[0] && cache_reslt
[0]) {
1007 condtests
[bl_unconsc
].test
= cache_reslt
[0];
1008 } else if (cache_avail
[1] && cache_reslt
[1]) {
1009 condtests
[bl_parlyz
].test
= cache_reslt
[1];
1010 } else if (condtests
[bl_sleeping
].enabled
&& u
.usleep
) {
1011 condtests
[bl_sleeping
].test
= TRUE
;
1012 } else if (condtests
[bl_busy
].enabled
) {
1013 condtests
[bl_busy
].test
= TRUE
;
1016 condtests
[bl_unconsc
].test
= condtests
[bl_parlyz
].test
=
1017 condtests
[bl_sleeping
].test
= condtests
[bl_busy
].test
= FALSE
;
1020 #define cond_setbit(c) \
1021 gb.blstats[idx][BL_CONDITION].a.a_ulong |= conditions[(c)].mask
1023 for (i
= 0; i
< CONDITION_COUNT
; ++i
) {
1024 if (condtests
[i
].enabled
1025 /* && i != bl_holding */ /* uncomment to suppress UHold */
1026 && condtests
[i
].test
)
1031 evaluate_and_notify_windowport(gv
.valset
, idx
);
1032 #undef test_if_enabled
1035 #undef cond_cache_prepA
1037 /* update just the status lines' 'time' field */
1039 stat_update_time(void)
1041 int idx
= gn
.now_or_before_idx
; /* no 0/1 toggle */
1045 gb
.blstats
[idx
][fld
].a
.a_long
= svm
.moves
;
1046 gv
.valset
[fld
] = FALSE
;
1048 eval_notify_windowport_field(fld
, gv
.valset
, idx
);
1049 if ((windowprocs
.wincap2
& WC2_FLUSH_STATUS
) != 0L)
1050 status_update(BL_FLUSH
, (genericptr_t
) 0, 0, 0,
1051 NO_COLOR
, (unsigned long *) 0);
1055 /* deal with player's choice to change processing of a condition */
1057 condopt(int idx
, boolean
*addr
, boolean negated
)
1062 if ((idx
< 0 || idx
>= CONDITION_COUNT
)
1063 || (addr
&& addr
!= &condtests
[idx
].choice
))
1067 /* special: indicates a request to init so
1068 set the choice values to match the defaults */
1069 gc
.condmenu_sortorder
= 0;
1070 for (i
= 0; i
< CONDITION_COUNT
; ++i
) {
1072 condtests
[i
].choice
= condtests
[i
].enabled
;
1074 qsort((genericptr_t
) cond_idx
, CONDITION_COUNT
,
1075 sizeof cond_idx
[0], cond_cmp
);
1077 /* (addr == &condtests[idx].choice) */
1078 condtests
[idx
].enabled
= negated
? FALSE
: TRUE
;
1079 condtests
[idx
].choice
= condtests
[idx
].enabled
;
1080 /* avoid lingering false positives if test is no longer run */
1081 condtests
[idx
].test
= FALSE
;
1085 /* qsort callback routine for sorting the condition index */
1086 staticfn
int QSORTCALLBACK
1087 cond_cmp(const genericptr vptr1
, const genericptr vptr2
)
1089 int indx1
= *(int *) vptr1
, indx2
= *(int *) vptr2
,
1090 c1
= conditions
[indx1
].ranking
, c2
= conditions
[indx2
].ranking
;
1094 /* tie-breaker - visible alpha by name */
1095 return strcmpi(condtests
[indx1
].useroption
, condtests
[indx2
].useroption
);
1098 /* qsort callback routine for alphabetical sorting of index */
1099 staticfn
int QSORTCALLBACK
1100 menualpha_cmp(const genericptr vptr1
, const genericptr vptr2
)
1102 int indx1
= *(int *) vptr1
, indx2
= *(int *) vptr2
;
1104 return strcmpi(condtests
[indx1
].useroption
, condtests
[indx2
].useroption
);
1108 parse_cond_option(boolean negated
, char *opts
)
1111 const char *compareto
, *uniqpart
, prefix
[] = "cond_";
1113 if (!opts
|| strlen(opts
) <= sizeof prefix
- 1)
1115 uniqpart
= opts
+ (sizeof prefix
- 1);
1116 for (i
= 0; i
< CONDITION_COUNT
; ++i
) {
1117 compareto
= condtests
[i
].useroption
;
1118 sl
= Strlen(compareto
);
1119 if (match_optname(uniqpart
, compareto
, (sl
>= 4) ? 4 : sl
, FALSE
)) {
1120 condopt(i
, &condtests
[i
].choice
, negated
);
1124 return 1; /* !0 indicates error */
1127 /* display a menu of all available status condition options and let player
1128 toggled them on or off; returns True iff any changes are made */
1132 static const char *const menutitle
[2] = {
1133 "alphabetically", "by ranking"
1135 int i
, res
, idx
= 0;
1136 int sequence
[CONDITION_COUNT
];
1139 menu_item
*picks
= (menu_item
*) 0;
1141 boolean showmenu
= TRUE
;
1143 boolean changed
= FALSE
;
1146 for (i
= 0; i
< CONDITION_COUNT
; ++i
) {
1149 qsort((genericptr_t
) sequence
, CONDITION_COUNT
,
1151 (gc
.condmenu_sortorder
) ? cond_cmp
: menualpha_cmp
);
1153 tmpwin
= create_nhwindow(NHW_MENU
);
1154 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
1158 Sprintf(mbuf
, "change sort order from \"%s\" to \"%s\"",
1159 menutitle
[gc
.condmenu_sortorder
],
1160 menutitle
[1 - gc
.condmenu_sortorder
]);
1161 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'S', 0, ATR_NONE
,
1162 clr
, mbuf
, MENU_ITEMFLAGS_SKIPINVERT
);
1164 Sprintf(mbuf
, "sorted %s", menutitle
[gc
.condmenu_sortorder
]);
1165 add_menu_heading(tmpwin
, mbuf
);
1166 for (i
= 0; i
< SIZE(condtests
); i
++) {
1168 Sprintf(mbuf
, "cond_%-14s", condtests
[idx
].useroption
);
1170 any
.a_int
= idx
+ 2; /* avoid zero and the sort change pick */
1171 condtests
[idx
].choice
= FALSE
;
1172 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
, clr
, mbuf
,
1173 condtests
[idx
].enabled
1174 ? MENU_ITEMFLAGS_SELECTED
: MENU_ITEMFLAGS_NONE
);
1177 end_menu(tmpwin
, "Choose status conditions to toggle");
1179 res
= select_menu(tmpwin
, PICK_ANY
, &picks
);
1180 destroy_nhwindow(tmpwin
);
1183 for (i
= 0; i
< res
; i
++) {
1184 idx
= picks
[i
].item
.a_int
;
1186 /* sort change requested */
1187 gc
.condmenu_sortorder
= 1 - gc
.condmenu_sortorder
;
1189 break; /* for loop */
1192 condtests
[idx
].choice
= TRUE
;
1195 free((genericptr_t
) picks
);
1200 for (i
= 0; i
< CONDITION_COUNT
; ++i
)
1201 if (condtests
[i
].enabled
!= condtests
[i
].choice
) {
1202 condtests
[i
].enabled
= condtests
[i
].choice
;
1203 condtests
[idx
].test
= FALSE
;
1204 disp
.botl
= changed
= TRUE
;
1210 /* called by all_options_conds() to get value for next cond_xyz option
1211 so that #saveoptions can collect it and write the set into new RC file.
1212 returns zero-length string if the option is the default value. */
1214 opt_next_cond(int indx
, char *outbuf
)
1217 if (indx
>= CONDITION_COUNT
)
1221 * The entries are returned in internal order which requires the
1222 * least code. It would be easy to sort them into alphabetic order
1223 * (just sort all over again for every requested entry:
1224 * int i, sequence[CONDITION_COUNT]
1225 * for (i = 0; i < CONDITION_COUNT; ++i) sequence[i] = i;
1226 * qsort(sequence, ..., menualpha_cmp);
1227 * indx = sequence[indx];
1228 * Sprintf(outbuf, ...);
1229 * with no need to hang on to 'sequence[]' between calls).
1231 * But using 'severity order' isn't feasible unless the player has
1232 * used 'mO' on conditions in this session. Even then, they would
1233 * revert to the default order (whether internal or alphabetical)
1234 * if #saveoptions got used in some later session where doset()
1235 * wasn't used to choose their preferred order.
1238 if ((condtests
[indx
].opt
== opt_in
&& condtests
[indx
].enabled
)
1239 || (condtests
[indx
].opt
== opt_out
&& !condtests
[indx
].enabled
)) {
1240 Sprintf(outbuf
, "%scond_%s", condtests
[indx
].enabled
? "" : "!",
1241 condtests
[indx
].useroption
);
1247 eval_notify_windowport_field(
1249 boolean
*valsetlist
,
1252 static int oldrndencode
= 0;
1253 static nhsym oldgoldsym
= 0;
1254 int pc
, chg
, color
= NO_COLOR
;
1256 boolean updated
= FALSE
, reset
;
1257 struct istat_s
*curr
, *prev
;
1258 enum statusfields fldmax
;
1261 * Now pass the changed values to window port.
1263 anytype
= gb
.blstats
[idx
][fld
].anytype
;
1264 curr
= &gb
.blstats
[idx
][fld
];
1265 prev
= &gb
.blstats
[1 - idx
][fld
];
1268 chg
= gu
.update_all
? 0 : compare_blstats(prev
, curr
);
1271 * Dynamically update 'percent_matters' as rules are added or
1272 * removed to track whether any of them are percentage rules.
1273 * Then there'll be no need to assume that non-Null 'thresholds'
1274 * means that percentages need to be kept up to date.
1275 * [Affects exp_percent_changing() too.]
1277 if (((chg
|| gu
.update_all
|| fld
== BL_XP
)
1278 && curr
->percent_matters
1279 #ifdef STATUS_HILITES
1283 /* when 'hitpointbar' is On, percent matters even if HP
1284 hasn't changed and has no percentage rules (in case HPmax
1285 has changed when HP hasn't, where we ordinarily wouldn't
1286 update HP so would miss an update of the hitpoint bar) */
1287 || (fld
== BL_HP
&& iflags
.wc2_hitpointbar
)) {
1288 fldmax
= curr
->idxmax
;
1289 pc
= (fldmax
== BL_EXP
) ? exp_percentage()
1290 : (fldmax
>= 0 && fldmax
< MAXBLSTATS
)
1291 ? percentage(curr
, &gb
.blstats
[idx
][fldmax
])
1292 : 0; /* bullet proofing; can't get here */
1293 if (pc
!= prev
->percent_value
)
1294 chg
= (pc
< prev
->percent_value
) ? -1 : 1;
1295 curr
->percent_value
= pc
;
1300 /* Temporary? hack: moveloop()'s prolog for a new game sets
1301 * svc.context.rndencode after the status window has been init'd,
1302 * so $:0 has already been encoded and cached by the window
1303 * port. Without this hack, gold's \G sequence won't be
1304 * recognized and ends up being displayed as-is for 'gu.update_all'.
1306 * Also, even if svc.context.rndencode hasn't changed and the
1307 * gold amount itself hasn't changed, the glyph portion of the
1308 * encoding may have changed if a new symset was put into effect.
1311 * XXXX = the svc.context.rndencode portion
1312 * NNNN = the glyph portion
1313 * 25 = the gold amount
1315 * Setting 'chg = 2' is enough to render the field properly, but
1316 * not to honor an initial highlight, so force 'gu.update_all = TRUE'.
1319 && (svc
.context
.rndencode
!= oldrndencode
1320 || gs
.showsyms
[COIN_CLASS
+ SYM_OFF_O
] != oldgoldsym
)) {
1321 gu
.update_all
= TRUE
; /* chg = 2; */
1322 oldrndencode
= svc
.context
.rndencode
;
1323 oldgoldsym
= gs
.showsyms
[COIN_CLASS
+ SYM_OFF_O
];
1327 #ifdef STATUS_HILITES
1328 if (!gu
.update_all
&& !chg
&& curr
->time
) {
1329 reset
= hilite_reset_needed(prev
, gb
.bl_hilite_moves
);
1331 curr
->time
= prev
->time
= 0L;
1335 if (gu
.update_all
|| chg
|| reset
) {
1336 if (!valsetlist
[fld
])
1337 (void) anything_to_s(curr
->val
, &curr
->a
, anytype
);
1339 if (anytype
!= ANY_MASK32
) {
1340 #ifdef STATUS_HILITES
1341 if (chg
|| *curr
->val
) {
1342 /* if Xp percentage changed, we set 'chg' to 1 above;
1343 reset that if the Xp value hasn't actually changed
1344 or possibly went down rather than up (level loss) */
1345 if (chg
== 1 && fld
== BL_XP
)
1346 chg
= compare_blstats(prev
, curr
);
1348 curr
->hilite_rule
= get_hilite(idx
, fld
,
1349 (genericptr_t
) &curr
->a
,
1351 prev
->hilite_rule
= curr
->hilite_rule
;
1357 #endif /* STATUS_HILITES */
1358 status_update(fld
, (genericptr_t
) curr
->val
,
1359 chg
, pc
, color
, (unsigned long *) 0);
1361 /* Color for conditions is done through gc.cond_hilites[] */
1362 status_update(fld
, (genericptr_t
) &curr
->a
.a_ulong
,
1363 chg
, pc
, color
, gc
.cond_hilites
);
1365 curr
->chg
= prev
->chg
= TRUE
;
1372 evaluate_and_notify_windowport(
1373 boolean
*valsetlist
,
1376 int i
, fld
, updated
= 0;
1379 * Now pass the changed values to window port.
1381 for (i
= 0; i
< MAXBLSTATS
; i
++) {
1382 fld
= initblstats
[i
].fld
;
1383 if (((fld
== BL_SCORE
) && !flags
.showscore
)
1384 || ((fld
== BL_EXP
) && !flags
.showexp
)
1385 || ((fld
== BL_TIME
) && !flags
.time
)
1386 || ((fld
== BL_HD
) && !Upolyd
)
1387 || ((fld
== BL_XP
|| i
== BL_EXP
) && Upolyd
)
1388 || ((fld
== BL_VERS
) && !flags
.showvers
)
1392 if (eval_notify_windowport_field(fld
, valsetlist
, idx
))
1397 * 1. It is possible to get here, with nothing having been pushed
1398 * to the window port, when none of the info has changed.
1400 * 2. Some window ports are also known to optimize by only drawing
1401 * fields that have changed since the previous update.
1403 * In both of those situations, we need to force updates to
1404 * all of the fields when disp.botlx is set. The tty port in
1405 * particular has a problem if that isn't done, since the core sets
1406 * disp.botlx when a menu or text display obliterates the status
1409 * For those situations, to trigger the full update of every field
1410 * whether changed or not, call status_update() with BL_RESET.
1412 * For regular processing and to notify the window port that a
1413 * bot() round has finished and it's time to trigger a flush of
1414 * all buffered changes received thus far but not reflected in
1415 * the display, call status_update() with BL_FLUSH.
1418 if (disp
.botlx
&& (windowprocs
.wincap2
& WC2_RESET_STATUS
) != 0L)
1419 status_update(BL_RESET
, (genericptr_t
) 0, 0, 0,
1420 NO_COLOR
, (unsigned long *) 0);
1421 else if ((updated
|| disp
.botlx
)
1422 && (windowprocs
.wincap2
& WC2_FLUSH_STATUS
) != 0L)
1423 status_update(BL_FLUSH
, (genericptr_t
) 0, 0, 0,
1424 NO_COLOR
, (unsigned long *) 0);
1426 disp
.botl
= disp
.botlx
= disp
.time_botl
= FALSE
;
1427 gu
.update_all
= FALSE
;
1432 boolean reassessment
) /* True: just recheck fields without other init */
1434 enum statusfields fld
;
1437 const char *fieldfmt
, *fieldname
;
1439 if (!reassessment
) {
1441 impossible("2nd status_initialize with full init.");
1443 (*windowprocs
.win_status_init
)();
1445 } else if (!gb
.blinit
) {
1446 panic("status 'reassess' before init");
1448 for (i
= 0; i
< MAXBLSTATS
; ++i
) {
1449 fld
= initblstats
[i
].fld
;
1450 fldenabl
= (fld
== BL_SCORE
) ? flags
.showscore
1451 : (fld
== BL_TIME
) ? flags
.time
1452 : (fld
== BL_EXP
) ? (boolean
) (flags
.showexp
&& !Upolyd
)
1453 : (fld
== BL_XP
) ? (boolean
) !Upolyd
1454 : (fld
== BL_HD
) ? (boolean
) Upolyd
1455 : (fld
== BL_VERS
) ? flags
.showvers
1458 fieldname
= initblstats
[i
].fldname
;
1459 fieldfmt
= (fld
== BL_TITLE
&& iflags
.wc2_hitpointbar
) ? "%-30.30s"
1460 : initblstats
[i
].fldfmt
;
1461 status_enablefield(fld
, fieldname
, fieldfmt
, fldenabl
);
1463 gu
.update_all
= TRUE
;
1472 /* call the window port cleanup routine first */
1473 if (windowprocs
.win_status_finish
)
1474 (*windowprocs
.win_status_finish
)();
1476 /* free memory that we alloc'd now */
1477 for (i
= 0; i
< MAXBLSTATS
; ++i
) {
1478 if (gb
.blstats
[0][i
].val
)
1479 free((genericptr_t
) gb
.blstats
[0][i
].val
),
1480 gb
.blstats
[0][i
].val
= (char *) NULL
;
1481 if (gb
.blstats
[1][i
].val
)
1482 free((genericptr_t
) gb
.blstats
[1][i
].val
),
1483 gb
.blstats
[1][i
].val
= (char *) NULL
;
1484 #ifdef STATUS_HILITES
1485 /* pointer to an entry in thresholds list; Null it out since
1486 that list is about to go away */
1487 gb
.blstats
[0][i
].hilite_rule
= gb
.blstats
[1][i
].hilite_rule
= 0;
1488 if (gb
.blstats
[0][i
].thresholds
) {
1489 struct hilite_s
*temp
, *next
;
1491 for (temp
= gb
.blstats
[0][i
].thresholds
; temp
; temp
= next
) {
1493 free((genericptr_t
) temp
);
1495 gb
.blstats
[0][i
].thresholds
1496 = gb
.blstats
[1][i
].thresholds
1497 = (struct hilite_s
*) NULL
;
1499 #endif /* STATUS_HILITES */
1506 static boolean initalready
= FALSE
;
1510 impossible("init_blstats called more than once.");
1513 for (i
= 0; i
<= 1; ++i
) {
1514 for (j
= 0; j
< MAXBLSTATS
; ++j
) {
1515 #ifdef STATUS_HILITES
1516 struct hilite_s
*keep_hilite_chain
= gb
.blstats
[i
][j
].thresholds
;
1519 gb
.blstats
[i
][j
] = initblstats
[j
];
1520 gb
.blstats
[i
][j
].a
= cg
.zeroany
;
1521 if (gb
.blstats
[i
][j
].valwidth
) {
1522 gb
.blstats
[i
][j
].val
1523 = (char *) alloc(gb
.blstats
[i
][j
].valwidth
);
1524 gb
.blstats
[i
][j
].val
[0] = '\0';
1526 gb
.blstats
[i
][j
].val
= (char *) 0;
1527 #ifdef STATUS_HILITES
1528 gb
.blstats
[i
][j
].thresholds
= keep_hilite_chain
;
1536 * This compares the previous stat with the current stat,
1537 * and returns one of the following results based on that:
1539 * if prev_value < new_value (stat went up, increased)
1542 * if prev_value > new_value (stat went down, decreased)
1545 * if prev_value == new_value (stat stayed the same)
1549 * - for bitmasks, 0 = stayed the same, 1 = changed
1550 * - for strings, 0 = stayed the same, 1 = changed
1554 compare_blstats(struct istat_s
*bl1
, struct istat_s
*bl2
)
1558 int anytype
, fld
, result
= 0;
1561 panic("compare_blstat: bad istat pointer %s, %s",
1562 fmt_ptr((genericptr_t
) bl1
), fmt_ptr((genericptr_t
) bl2
));
1565 anytype
= bl1
->anytype
;
1566 if ((!bl1
->a
.a_void
|| !bl2
->a
.a_void
)
1567 && (anytype
== ANY_IPTR
|| anytype
== ANY_UPTR
1568 || anytype
== ANY_LPTR
|| anytype
== ANY_ULPTR
)) {
1569 panic("compare_blstat: invalid pointer %s, %s",
1570 fmt_ptr((genericptr_t
) bl1
->a
.a_void
),
1571 fmt_ptr((genericptr_t
) bl2
->a
.a_void
));
1575 use_rawval
= (fld
== BL_HP
|| fld
== BL_HPMAX
1576 || fld
== BL_ENE
|| fld
== BL_ENEMAX
1578 a1
= use_rawval
? &bl1
->rawval
: &bl1
->a
;
1579 a2
= use_rawval
? &bl2
->rawval
: &bl2
->a
;
1583 result
= (a1
->a_int
< a2
->a_int
) ? 1
1584 : (a1
->a_int
> a2
->a_int
) ? -1 : 0;
1587 result
= (*a1
->a_iptr
< *a2
->a_iptr
) ? 1
1588 : (*a1
->a_iptr
> *a2
->a_iptr
) ? -1 : 0;
1591 result
= (a1
->a_long
< a2
->a_long
) ? 1
1592 : (a1
->a_long
> a2
->a_long
) ? -1 : 0;
1595 result
= (*a1
->a_lptr
< *a2
->a_lptr
) ? 1
1596 : (*a1
->a_lptr
> *a2
->a_lptr
) ? -1 : 0;
1599 result
= (a1
->a_uint
< a2
->a_uint
) ? 1
1600 : (a1
->a_uint
> a2
->a_uint
) ? -1 : 0;
1603 result
= (*a1
->a_uptr
< *a2
->a_uptr
) ? 1
1604 : (*a1
->a_uptr
> *a2
->a_uptr
) ? -1 : 0;
1607 result
= (a1
->a_ulong
< a2
->a_ulong
) ? 1
1608 : (a1
->a_ulong
> a2
->a_ulong
) ? -1 : 0;
1611 result
= (*a1
->a_ulptr
< *a2
->a_ulptr
) ? 1
1612 : (*a1
->a_ulptr
> *a2
->a_ulptr
) ? -1 : 0;
1615 result
= sgn(strcmp(bl1
->val
, bl2
->val
));
1618 result
= (a1
->a_ulong
!= a2
->a_ulong
);
1627 anything_to_s(char *buf
, anything
*a
, int anytype
)
1634 Sprintf(buf
, "%lu", a
->a_ulong
);
1637 Sprintf(buf
, "%lx", a
->a_ulong
);
1640 Sprintf(buf
, "%ld", a
->a_long
);
1643 Sprintf(buf
, "%d", a
->a_int
);
1646 Sprintf(buf
, "%u", a
->a_uint
);
1649 Sprintf(buf
, "%d", *a
->a_iptr
);
1652 Sprintf(buf
, "%ld", *a
->a_lptr
);
1655 Sprintf(buf
, "%lu", *a
->a_ulptr
);
1658 Sprintf(buf
, "%u", *a
->a_uptr
);
1660 case ANY_STR
: /* do nothing */
1669 #ifdef STATUS_HILITES
1671 s_to_anything(anything
*a
, char *buf
, int anytype
)
1678 a
->a_long
= atol(buf
);
1681 a
->a_int
= atoi(buf
);
1684 a
->a_uint
= (unsigned) atoi(buf
);
1687 a
->a_ulong
= (unsigned long) atol(buf
);
1691 *a
->a_iptr
= atoi(buf
);
1695 *a
->a_uptr
= (unsigned) atoi(buf
);
1699 *a
->a_lptr
= atol(buf
);
1703 *a
->a_ulptr
= (unsigned long) atol(buf
);
1706 a
->a_ulong
= (unsigned long) atol(buf
);
1714 #endif /* STATUS_HILITES */
1716 /* integer percentage is 100 * bl->a / maxbl->a */
1718 percentage(struct istat_s
*bl
, struct istat_s
*maxbl
)
1725 unsigned long ulval
;
1729 if (!bl
|| !maxbl
) {
1730 impossible("percentage: bad istat pointer %s, %s",
1731 fmt_ptr((genericptr_t
) bl
), fmt_ptr((genericptr_t
) maxbl
));
1736 use_rawval
= (fld
== BL_HP
|| fld
== BL_ENE
);
1737 ival
= 0, lval
= 0L, uval
= 0U, ulval
= 0UL;
1738 anytype
= bl
->anytype
;
1739 if (maxbl
->a
.a_void
) {
1742 /* HP and energy are int so this is the only case that cares
1743 about 'rawval'; for them, we use that rather than their
1744 potentially truncated (to 9999) display value */
1745 ival
= use_rawval
? bl
->rawval
.a_int
: bl
->a
.a_int
;
1746 mval
= use_rawval
? maxbl
->rawval
.a_int
: maxbl
->a
.a_int
;
1747 result
= ((100 * ival
) / mval
);
1750 lval
= bl
->a
.a_long
;
1751 result
= (int) ((100L * lval
) / maxbl
->a
.a_long
);
1754 uval
= bl
->a
.a_uint
;
1755 result
= (int) ((100U * uval
) / maxbl
->a
.a_uint
);
1758 ulval
= bl
->a
.a_ulong
;
1759 result
= (int) ((100UL * ulval
) / maxbl
->a
.a_ulong
);
1762 ival
= *bl
->a
.a_iptr
;
1763 result
= ((100 * ival
) / (*maxbl
->a
.a_iptr
));
1766 lval
= *bl
->a
.a_lptr
;
1767 result
= (int) ((100L * lval
) / (*maxbl
->a
.a_lptr
));
1770 uval
= *bl
->a
.a_uptr
;
1771 result
= (int) ((100U * uval
) / (*maxbl
->a
.a_uptr
));
1774 ulval
= *bl
->a
.a_ulptr
;
1775 result
= (int) ((100UL * ulval
) / (*maxbl
->a
.a_ulptr
));
1779 /* don't let truncation from integer division produce a zero result
1780 from a non-zero input; note: if we ever change to something like
1781 ((((1000 * val) / max) + 5) / 10) for a rounded result, we'll
1782 also need to check for and convert false 100 to 99 */
1784 && (ival
!= 0 || lval
!= 0L || uval
!= 0U || ulval
!= 0UL))
1790 /* percentage for both xp (level) and exp (points) is the percentage for
1791 (curr_exp - this_level_start) in (next_level_start - this_level_start) */
1793 exp_percentage(void)
1797 if (u
.ulevel
< 30) {
1798 long exp_val
, nxt_exp_val
, curlvlstart
;
1800 curlvlstart
= newuexp(u
.ulevel
- 1);
1801 exp_val
= u
.uexp
- curlvlstart
;
1802 nxt_exp_val
= newuexp(u
.ulevel
) - curlvlstart
;
1803 if (exp_val
== nxt_exp_val
- 1L) {
1805 * Full 100% is unattainable since hero gains a level
1806 * and the threshold for next level increases, but treat
1807 * (next_level_start - 1 point) as a special case. It's a
1808 * key value after being level drained so is something that
1809 * some players would like to be able to highlight distinctly.
1813 struct istat_s curval
, maxval
;
1815 curval
.anytype
= maxval
.anytype
= ANY_LONG
;
1816 curval
.a
= maxval
.a
= cg
.zeroany
;
1817 curval
.a
.a_long
= exp_val
;
1818 maxval
.a
.a_long
= nxt_exp_val
;
1819 curval
.fld
= maxval
.fld
= BL_EXP
; /* (neither BL_HP nor BL_ENE) */
1820 /* maximum delta between levels is 10000000; calculation of
1821 100 * (10000000 - N) / 10000000 fits within 32-bit long */
1822 res
= percentage(&curval
, &maxval
);
1828 /* experience points have changed but experience level hasn't; decide whether
1829 botl update is needed for a different percentage highlight rule for Xp */
1831 exp_percent_changing(void)
1835 #ifdef STATUS_HILITES
1837 struct hilite_s
*rule
;
1839 struct istat_s
*curr
;
1841 /* if status update is already requested, skip this processing */
1844 * Status update is warranted iff percent integer changes and the new
1845 * percentage results in a different highlighting rule being selected.
1847 curr
= &gb
.blstats
[gn
.now_or_before_idx
][BL_XP
];
1848 /* TODO: [see eval_notify_windowport_field() about percent_matters
1849 and the check against 'thresholds'] */
1850 if (curr
->percent_matters
1851 #ifdef STATUS_HILITES
1854 && (pc
= exp_percentage()) != curr
->percent_value
) {
1856 a
.a_int
= (int) u
.ulevel
;
1857 #ifdef STATUS_HILITES
1858 rule
= get_hilite(gn
.now_or_before_idx
, BL_XP
,
1859 (genericptr_t
) &a
, 0, pc
, &color_dummy
);
1860 if (rule
!= curr
->hilite_rule
)
1861 return TRUE
; /* caller should set 'disp.botl' to True */
1868 /* callback so that interface can get capacity index rather than trying
1869 to reconstruct that from the encumbrance string or asking the general
1870 core what the value is */
1876 #ifdef STATUS_HILITES
1877 cap
= gb
.blstats
[gn
.now_or_before_idx
][BL_CAP
].a
.a_int
;
1879 cap
= near_capacity();
1884 /* callback so that interface can get hunger index rather than trying to
1885 reconstruct that from the hunger string or dipping into core internals */
1887 stat_hunger_indx(void)
1891 #ifdef STATUS_HILITES
1892 uhs
= gb
.blstats
[gn
.now_or_before_idx
][BL_HUNGER
].a
.a_int
;
1899 /* used by X11 for "tty status" even when STATUS_HILITES is disabled */
1901 bl_idx_to_fldname(int idx
)
1903 if (idx
>= 0 && idx
< MAXBLSTATS
)
1904 return initblstats
[idx
].fldname
;
1905 return (const char *) 0;
1908 /* used when rendering hitpointbar; inoutbuf[] has been padded with
1909 trailing spaces; replace pairs of spaces with pairs of space+dash */
1911 repad_with_dashes(char *inoutbuf
)
1913 char *p
= eos(inoutbuf
);
1915 while (p
>= inoutbuf
+ 2 && p
[-1] == ' ' && p
[-2] == ' ') {
1921 #ifdef STATUS_HILITES
1923 /****************************************************************************/
1924 /* Core status hiliting support */
1925 /****************************************************************************/
1927 static const struct fieldid_t
{
1928 const char *fieldname
;
1929 enum statusfields fldid
;
1930 } fieldids_alias
[] = {
1931 { "characteristics", BL_CHARACTERISTICS
},
1932 { "encumbrance", BL_CAP
},
1933 { "experience-points", BL_EXP
},
1937 { "points", BL_SCORE
},
1940 { "pw-max", BL_ENEMAX
},
1944 { "hit-dice", BL_HD
},
1945 { "turns", BL_TIME
},
1947 { "hp-max", BL_HPMAX
},
1948 { "dgn", BL_LEVELDESC
},
1951 { "flags", BL_CONDITION
},
1955 /* format arguments */
1956 static const char threshold_value
[] = "hilite_status threshold ",
1957 is_out_of_range
[] = " is out of range";
1960 /* field name to bottom line index */
1961 staticfn
enum statusfields
1962 fldname_to_bl_indx(const char *name
)
1964 int i
, nmatches
= 0, fld
= 0;
1966 if (name
&& *name
) {
1967 /* check matches to canonical names */
1968 for (i
= 0; i
< SIZE(initblstats
); i
++)
1969 if (fuzzymatch(initblstats
[i
].fldname
, name
, " -_", TRUE
)) {
1970 fld
= initblstats
[i
].fld
;
1975 for (i
= 0; fieldids_alias
[i
].fieldname
; i
++)
1976 if (fuzzymatch(fieldids_alias
[i
].fieldname
, name
,
1978 fld
= fieldids_alias
[i
].fldid
;
1983 /* check partial matches to canonical names */
1984 int len
= (int) strlen(name
);
1986 for (i
= 0; i
< SIZE(initblstats
); i
++)
1987 if (!strncmpi(name
, initblstats
[i
].fldname
, len
)) {
1988 fld
= initblstats
[i
].fld
;
1994 return (nmatches
== 1) ? fld
: BL_FLUSH
;
1998 hilite_reset_needed(
1999 struct istat_s
*bl_p
,
2000 long augmented_time
) /* no longer augmented; it once encoded fractional
2001 * amounts for multiple moves within same turn */
2004 * This 'multi' handling may need some tuning...
2009 if (!Is_Temp_Hilite(bl_p
->hilite_rule
))
2012 if (bl_p
->time
== 0 || bl_p
->time
>= augmented_time
)
2018 /* called from moveloop(); sets context.botl if temp hilites have timed out */
2020 status_eval_next_unhilite(void)
2023 struct istat_s
*curr
;
2024 long next_unhilite
, this_unhilite
;
2026 gb
.bl_hilite_moves
= svm
.moves
; /* simplified; at one point we used to
2027 * try to encode fractional amounts for
2028 * multiple moves within same turn */
2029 /* figure out whether an unhilight needs to be performed now */
2031 for (i
= 0; i
< MAXBLSTATS
; ++i
) {
2032 curr
= &gb
.blstats
[0][i
]; /* blstats[0][*].time==blstats[1][*].time */
2035 struct istat_s
*prev
= &gb
.blstats
[1][i
];
2037 if (Is_Temp_Hilite(curr
->hilite_rule
))
2038 curr
->time
= (gb
.bl_hilite_moves
+ iflags
.hilite_delta
);
2041 prev
->time
= curr
->time
;
2043 curr
->chg
= prev
->chg
= FALSE
;
2047 continue; /* just process other gb.blstats[][].time and .chg */
2049 this_unhilite
= curr
->time
;
2050 if (this_unhilite
> 0L
2051 && (next_unhilite
== 0L || this_unhilite
< next_unhilite
)
2052 && hilite_reset_needed(curr
, this_unhilite
+ 1L)) {
2053 next_unhilite
= this_unhilite
;
2054 if (next_unhilite
< gb
.bl_hilite_moves
)
2060 /* called by options handling when 'statushilites' value is changed */
2062 reset_status_hilites(void)
2064 if (iflags
.hilite_delta
) {
2067 for (i
= 0; i
< MAXBLSTATS
; ++i
)
2068 gb
.blstats
[0][i
].time
= gb
.blstats
[1][i
].time
= 0L;
2069 gu
.update_all
= TRUE
;
2074 /* test whether the text from a title rule matches the string for
2075 title-while-polymorphed in the 'textmatch' menu */
2077 noneoftheabove(const char *hl_text
)
2079 if (fuzzymatch(hl_text
, "none of the above", "\" -_", TRUE
)
2080 || fuzzymatch(hl_text
, "(polymorphed)", "\"()", TRUE
)
2081 || fuzzymatch(hl_text
, "none of the above (polymorphed)",
2090 * Returns, based on the value and the direction it is moving,
2091 * the highlight rule that applies to the specified field.
2093 * Provide get_hilite() with the following to work with:
2095 * useful for BL_TH_VAL_ABSOLUTE
2096 * indicator of down, up, or the same (-1, 1, 0) chg
2097 * useful for BL_TH_UPDOWN or change detection
2098 * percentage (current value percentage of max value) pc
2099 * useful for BL_TH_VAL_PERCENTAGE
2102 * pointer to rule that applies; Null if no rule does.
2104 staticfn
struct hilite_s
*
2106 int idx
, int fldidx
,
2111 struct hilite_s
*hl
, *rule
= 0;
2112 anything
*value
= (anything
*) vp
;
2115 if (fldidx
< 0 || fldidx
>= MAXBLSTATS
)
2116 return (struct hilite_s
*) 0;
2118 if (has_hilite(fldidx
)) {
2120 /* there are hilites set here */
2121 int max_pc
= -1, min_pc
= 101;
2122 /* LARGEST_INT isn't INT_MAX; it fits within 16 bits, but that
2123 value is big enough to handle all 'int' status fields */
2124 int max_ival
= -LARGEST_INT
, min_ival
= LARGEST_INT
;
2125 /* LONG_MAX comes from <limits.h> which might not be available for
2126 ancient configurations; we don't need LONG_MIN */
2127 long max_lval
= -LONG_MAX
, min_lval
= LONG_MAX
;
2128 boolean exactmatch
= FALSE
, updown
= FALSE
, changed
= FALSE
,
2129 perc_or_abs
= FALSE
, crit_hp
= FALSE
;
2131 /* min_/max_ are used to track best fit */
2132 for (hl
= gb
.blstats
[0][fldidx
].thresholds
; hl
; hl
= hl
->next
) {
2133 dt
= initblstats
[fldidx
].anytype
; /* only needed for 'absolute' */
2134 /* for HP, if we already have a critical-hp rule then we ignore
2135 other HP rules unless we hit another critical-hp one (last
2136 one found wins); critical-hp takes precedence over temporary
2137 HP highlights, otherwise a hero with regeneration and an up
2138 or changed rule for HP would always show that up or changed
2139 highlight even when within the critical-hp threshold because
2140 the value will go up by at least one on every move */
2141 if (crit_hp
&& hl
->behavior
!= BL_TH_CRITICALHP
)
2143 /* if we've already matched a temporary highlight, it takes
2144 precedence over all persistent ones; we still process
2145 updown rules to get the last one which qualifies */
2146 if ((updown
|| changed
) && hl
->behavior
!= BL_TH_UPDOWN
)
2148 /* among persistent highlights, if a 'percentage' or 'absolute'
2149 rule has been matched, it takes precedence over 'always' */
2150 if (perc_or_abs
&& hl
->behavior
== BL_TH_ALWAYS_HILITE
)
2153 switch (hl
->behavior
) {
2154 case BL_TH_VAL_PERCENTAGE
: /* percent values are always ANY_INT */
2155 if (hl
->rel
== EQ_VALUE
&& pc
== hl
->value
.a_int
) {
2157 min_pc
= max_pc
= hl
->value
.a_int
;
2158 exactmatch
= perc_or_abs
= TRUE
;
2159 } else if (exactmatch
) {
2160 ; /* already found best fit, skip lt,ge,&c */
2161 } else if (hl
->rel
== LT_VALUE
2162 && (pc
< hl
->value
.a_int
)
2163 && (hl
->value
.a_int
<= min_pc
)) {
2165 min_pc
= hl
->value
.a_int
;
2167 } else if (hl
->rel
== LE_VALUE
2168 && (pc
<= hl
->value
.a_int
)
2169 && (hl
->value
.a_int
<= min_pc
)) {
2171 min_pc
= hl
->value
.a_int
;
2173 } else if (hl
->rel
== GT_VALUE
2174 && (pc
> hl
->value
.a_int
)
2175 && (hl
->value
.a_int
>= max_pc
)) {
2177 max_pc
= hl
->value
.a_int
;
2179 } else if (hl
->rel
== GE_VALUE
2180 && (pc
>= hl
->value
.a_int
)
2181 && (hl
->value
.a_int
>= max_pc
)) {
2183 max_pc
= hl
->value
.a_int
;
2187 case BL_TH_UPDOWN
: /* uses 'chg' (set by caller), not 'dt' */
2188 /* specific 'up' or 'down' takes precedence over general
2189 'changed' regardless of their order in the rule set */
2190 if (chg
< 0 && hl
->rel
== LT_VALUE
) {
2193 } else if (chg
> 0 && hl
->rel
== GT_VALUE
) {
2196 } else if (chg
!= 0 && hl
->rel
== EQ_VALUE
&& !updown
) {
2201 case BL_TH_VAL_ABSOLUTE
: /* either ANY_INT or ANY_LONG */
2203 * The int and long variations here are identical aside from
2204 * union field and min_/max_ variable names. If you change
2205 * one, be sure to make a corresponding change in the other.
2207 if (dt
== ANY_INT
) {
2208 if (hl
->rel
== EQ_VALUE
2209 && hl
->value
.a_int
== value
->a_int
) {
2211 min_ival
= max_ival
= hl
->value
.a_int
;
2212 exactmatch
= perc_or_abs
= TRUE
;
2213 } else if (exactmatch
) {
2214 ; /* already found best fit, skip lt,ge,&c */
2215 } else if (hl
->rel
== LT_VALUE
2216 && (value
->a_int
< hl
->value
.a_int
)
2217 && (hl
->value
.a_int
<= min_ival
)) {
2219 min_ival
= hl
->value
.a_int
;
2221 } else if (hl
->rel
== LE_VALUE
2222 && (value
->a_int
<= hl
->value
.a_int
)
2223 && (hl
->value
.a_int
<= min_ival
)) {
2225 min_ival
= hl
->value
.a_int
;
2227 } else if (hl
->rel
== GT_VALUE
2228 && (value
->a_int
> hl
->value
.a_int
)
2229 && (hl
->value
.a_int
>= max_ival
)) {
2231 max_ival
= hl
->value
.a_int
;
2233 } else if (hl
->rel
== GE_VALUE
2234 && (value
->a_int
>= hl
->value
.a_int
)
2235 && (hl
->value
.a_int
>= max_ival
)) {
2237 max_ival
= hl
->value
.a_int
;
2240 } else { /* ANY_LONG */
2241 if (hl
->rel
== EQ_VALUE
2242 && hl
->value
.a_long
== value
->a_long
) {
2244 min_lval
= max_lval
= hl
->value
.a_long
;
2245 exactmatch
= perc_or_abs
= TRUE
;
2246 } else if (exactmatch
) {
2247 ; /* already found best fit, skip lt,ge,&c */
2248 } else if (hl
->rel
== LT_VALUE
2249 && (value
->a_long
< hl
->value
.a_long
)
2250 && (hl
->value
.a_long
<= min_lval
)) {
2252 min_lval
= hl
->value
.a_long
;
2254 } else if (hl
->rel
== LE_VALUE
2255 && (value
->a_long
<= hl
->value
.a_long
)
2256 && (hl
->value
.a_long
<= min_lval
)) {
2258 min_lval
= hl
->value
.a_long
;
2260 } else if (hl
->rel
== GT_VALUE
2261 && (value
->a_long
> hl
->value
.a_long
)
2262 && (hl
->value
.a_long
>= max_lval
)) {
2264 max_lval
= hl
->value
.a_long
;
2266 } else if (hl
->rel
== GE_VALUE
2267 && (value
->a_long
>= hl
->value
.a_long
)
2268 && (hl
->value
.a_long
>= max_lval
)) {
2270 max_lval
= hl
->value
.a_long
;
2275 case BL_TH_TEXTMATCH
: /* ANY_STR */
2276 txtstr
= gb
.blstats
[idx
][fldidx
].val
;
2277 if (fldidx
== BL_TITLE
)
2278 /* "<name> the <rank-title>", skip past "<name> the " */
2279 txtstr
+= strlen(svp
.plname
) + sizeof " the " - sizeof "";
2280 if (hl
->rel
== TXT_VALUE
&& hl
->textmatch
[0]) {
2281 if (fuzzymatch(hl
->textmatch
, txtstr
, "\" -_", TRUE
)) {
2284 } else if (exactmatch
) {
2285 ; /* already found best fit, skip "noneoftheabove" */
2286 } else if (fldidx
== BL_TITLE
2287 && Upolyd
&& noneoftheabove(hl
->textmatch
)) {
2292 case BL_TH_ALWAYS_HILITE
:
2295 case BL_TH_CRITICALHP
:
2296 if (fldidx
== BL_HP
&& critically_low_hp(FALSE
)) {
2299 updown
= changed
= perc_or_abs
= FALSE
;
2309 *colorptr
= rule
? rule
->coloridx
: NO_COLOR
;
2314 #undef Is_Temp_Hilite
2317 split_clridx(int idx
, int *coloridx
, int *attrib
)
2320 *coloridx
= idx
& 0x00FF;
2322 *attrib
= (idx
>> 8) & 0x00FF;
2326 * This is the parser for the hilite options.
2328 * parse_status_hl1() separates each hilite entry into
2329 * a set of field threshold/action component strings,
2330 * then calls parse_status_hl2() to parse further
2331 * and configure the hilite.
2334 parse_status_hl1(char *op
, boolean from_configfile
)
2336 #define MAX_THRESH 21
2337 char hsbuf
[MAX_THRESH
][QBUFSZ
];
2338 boolean rslt
, badopt
= FALSE
;
2339 int i
, fldnum
, ccount
= 0;
2343 for (i
= 0; i
< MAX_THRESH
; ++i
) {
2346 while (*op
&& fldnum
< MAX_THRESH
&& ccount
< (QBUFSZ
- 2)) {
2350 if (fldnum
== 1 && strcmpi(hsbuf
[0], "title") == 0) {
2351 /* spaces are allowed in title */
2352 hsbuf
[fldnum
][ccount
++] = c
;
2353 hsbuf
[fldnum
][ccount
] = '\0';
2357 rslt
= parse_status_hl2(hsbuf
, from_configfile
);
2363 for (i
= 0; i
< MAX_THRESH
; ++i
) {
2368 } else if (c
== '/') {
2372 hsbuf
[fldnum
][ccount
++] = c
;
2373 hsbuf
[fldnum
][ccount
] = '\0';
2377 if (fldnum
>= 1 && !badopt
) {
2378 rslt
= parse_status_hl2(hsbuf
, from_configfile
);
2384 /* make sure highlighting is On; use short duration for temp highlights */
2385 if (!iflags
.hilite_delta
)
2386 iflags
.hilite_delta
= 3L;
2391 /* is str in the format of "[<>]?=?[-+]?[0-9]+%?" regex */
2393 is_ltgt_percentnumber(const char *str
)
2395 const char *s
= str
;
2397 if (*s
== '<' || *s
== '>')
2401 if (*s
== '-' || *s
== '+')
2409 return (*s
== '\0');
2412 /* does str only contain "<>=-+0-9%" chars */
2414 has_ltgt_percentnumber(const char *str
)
2416 const char *s
= str
;
2419 if (!strchr("<>=-+0123456789%", *s
))
2426 /* splitsubfields(): splits str in place into '+' or '&' separated strings.
2427 * returns number of strings, or -1 if more than maxsf or MAX_SUBFIELDS
2430 splitsubfields(char *str
, char ***sfarr
, int maxsf
)
2432 #define MAX_SUBFIELDS 16
2433 static char *subfields
[MAX_SUBFIELDS
];
2434 char *st
= (char *) 0;
2439 for (sf
= 0; sf
< MAX_SUBFIELDS
; ++sf
)
2440 subfields
[sf
] = (char *) 0;
2442 maxsf
= (maxsf
== 0) ? MAX_SUBFIELDS
: min(maxsf
, MAX_SUBFIELDS
);
2444 if (strchr(str
, '+') || strchr(str
, '&')) {
2449 while (*c
&& sf
< maxsf
) {
2450 if (*c
== '&' || *c
== '+') {
2458 if (sf
>= maxsf
- 1)
2461 subfields
[sf
++] = st
;
2468 #undef MAX_SUBFIELDS
2474 const char *const *arr
,
2475 int arrmin
, int arrmax
,
2480 for (i
= arrmin
; i
< arrmax
; i
++)
2481 if (!strcmpi(str
, arr
[i
])) {
2490 const char *querystr
,
2491 const char *const *arr
,
2492 int arrmin
, int arrmax
)
2494 int i
, res
, ret
= arrmin
- 1;
2497 menu_item
*picks
= (menu_item
*) 0;
2498 int adj
= (arrmin
> 0) ? 1 : arrmax
;
2501 tmpwin
= create_nhwindow(NHW_MENU
);
2502 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
2504 for (i
= arrmin
; i
< arrmax
; i
++) {
2505 if (!arr
[i
]) /* the array of hunger status values has a gap ...*/
2506 continue; /*... set to Null between Satiated and Hungry */
2508 any
.a_int
= i
+ adj
;
2509 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
2510 clr
, arr
[i
], MENU_ITEMFLAGS_NONE
);
2513 end_menu(tmpwin
, querystr
);
2515 res
= select_menu(tmpwin
, PICK_ONE
, &picks
);
2516 destroy_nhwindow(tmpwin
);
2518 ret
= picks
->item
.a_int
- adj
;
2519 free((genericptr_t
) picks
);
2526 status_hilite_add_threshold(int fld
, struct hilite_s
*hilite
)
2528 struct hilite_s
*new_hilite
, *old_hilite
;
2533 /* alloc and initialize a new hilite_s struct */
2534 new_hilite
= (struct hilite_s
*) alloc(sizeof (struct hilite_s
));
2535 *new_hilite
= *hilite
; /* copy struct */
2537 new_hilite
->set
= TRUE
;
2538 new_hilite
->fld
= fld
;
2539 new_hilite
->next
= (struct hilite_s
*) 0;
2540 /* insert new entry at the end of the list */
2541 if (!gb
.blstats
[0][fld
].thresholds
) {
2542 gb
.blstats
[0][fld
].thresholds
= new_hilite
;
2544 for (old_hilite
= gb
.blstats
[0][fld
].thresholds
; old_hilite
->next
;
2545 old_hilite
= old_hilite
->next
)
2547 old_hilite
->next
= new_hilite
;
2549 /* sort_hilites(fld) */
2551 /* current and prev must both point at the same hilites */
2552 gb
.blstats
[1][fld
].thresholds
= gb
.blstats
[0][fld
].thresholds
;
2556 parse_status_hl2(char (*s
)[QBUFSZ
], boolean from_configfile
)
2558 static const char *const aligntxt
[] = { "chaotic", "neutral", "lawful" };
2559 /* hu_stat[] from eat.c has trailing spaces which foul up comparisons;
2560 for the "not hungry" case, there's no text hence no way to highlight */
2561 static const char *const hutxt
[] = {
2562 "Satiated", "", "Hungry", "Weak", "Fainting", "Fainted", "Starved"
2565 int sidx
= 0, i
= -1, dt
= -1;
2566 int coloridx
= -1, successes
= 0;
2567 int disp_attrib
= 0;
2568 boolean percent
, changed
, numeric
, down
, up
,
2569 grt
, lt
, gte
, le
, eq
, txtval
, always
, criticalhp
;
2571 enum statusfields fld
= BL_FLUSH
;
2572 struct hilite_s hilite
;
2577 OPTION=hilite_status: hitpoints/<10%/red
2578 OPTION=hilite_status: hitpoints/<10%/red/<5%/purple/1/red+blink+inverse
2579 OPTION=hilite_status: experience/down/red/up/green
2580 OPTION=hilite_status: cap/strained/yellow/overtaxed/orange
2581 OPTION=hilite_status: title/always/blue
2582 OPTION=hilite_status: title/blue
2585 /* field name to statusfield */
2586 fld
= fldname_to_bl_indx(s
[sidx
]);
2588 if (fld
== BL_CHARACTERISTICS
) {
2589 boolean res
= FALSE
;
2591 /* recursively set each of strength, dexterity, constitution, &c */
2592 for (fld
= BL_STR
; fld
<= BL_CH
; fld
++) {
2593 Strcpy(s
[sidx
], initblstats
[fld
].fldname
);
2594 res
= parse_status_hl2(s
, from_configfile
);
2600 if (fld
== BL_FLUSH
) {
2601 config_error_add("Unknown status field '%s'", s
[sidx
]);
2604 if (fld
== BL_CONDITION
)
2605 return parse_condition(s
, sidx
);
2608 while (s
[sidx
][0]) {
2609 char buf
[BUFSZ
], **subfields
;
2610 int sf
= 0; /* subfield count */
2613 txt
= (const char *) 0;
2614 percent
= numeric
= always
= FALSE
;
2615 down
= up
= changed
= FALSE
;
2617 grt
= gte
= eq
= le
= lt
= txtval
= FALSE
;
2619 /* threshold value - return on empty string */
2623 memset((genericptr_t
) &hilite
, 0, sizeof (struct hilite_s
));
2624 hilite
.set
= FALSE
; /* mark it "unset" */
2627 if (*s
[sidx
+ 1] == '\0' || !strcmpi(s
[sidx
], "always")) {
2628 /* "field/always/color" OR "field/color" */
2630 if (*s
[sidx
+ 1] == '\0')
2632 } else if (!strcmpi(s
[sidx
], "up") || !strcmpi(s
[sidx
], "down")) {
2633 if (initblstats
[fld
].anytype
== ANY_STR
)
2634 /* ordered string comparison is supported but LT/GT for
2635 the string fields (title, dungeon-level, alignment)
2636 is pointless; treat 'up' or 'down' for string fields
2637 as 'changed' rather than rejecting them outright */
2639 else if (!strcmpi(s
[sidx
], "down"))
2644 } else if (fld
== BL_CAP
2645 && is_fld_arrayvalues(s
[sidx
], enc_stat
,
2646 SLT_ENCUMBER
, OVERLOADED
+ 1,
2648 txt
= enc_stat
[kidx
];
2650 } else if (fld
== BL_ALIGN
2651 && is_fld_arrayvalues(s
[sidx
], aligntxt
, 0, 3, &kidx
)) {
2652 txt
= aligntxt
[kidx
];
2654 } else if (fld
== BL_HUNGER
2655 && is_fld_arrayvalues(s
[sidx
], hutxt
,
2656 SATIATED
, STARVED
+ 1, &kidx
)) {
2657 txt
= hu_stat
[kidx
]; /* store hu_stat[] val, not hutxt[] */
2659 } else if (!strcmpi(s
[sidx
], "changed")) {
2661 } else if (fld
== BL_HP
&& !strcmpi(s
[sidx
], "criticalhp")) {
2663 } else if (is_ltgt_percentnumber(s
[sidx
])) {
2666 tmp
= s
[sidx
]; /* is_ltgt_() guarantees [<>]?=?[-+]?[0-9]+%? */
2667 if (strchr(tmp
, '%'))
2674 } else if (*tmp
== '>') {
2680 /* '%', '<', '>' have served their purpose, '=' is either
2681 part of '<' or '>' or optional for '=N', unary '+' is
2682 just decorative, so get rid of them, leaving -?[0-9]+ */
2683 tmp
= stripchars(tmpbuf
, "%<>=+", tmp
);
2685 dt
= percent
? ANY_INT
: initblstats
[fld
].anytype
;
2686 (void) s_to_anything(&hilite
.value
, tmp
, dt
);
2688 op
= grt
? ">" : gte
? ">=" : lt
? "<" : le
? "<=" : "=";
2690 /* AC is the only field where negative values make sense but
2691 accept >-1 for other fields; reject <0 for non-AC */
2692 && (hilite
.value
.a_int
2693 < ((fld
== BL_AC
) ? -128 : grt
? -1 : lt
? 1 : 0)
2694 /* percentages have another more comprehensive check below */
2695 || hilite
.value
.a_int
> (percent
? (lt
? 101 : 100)
2697 config_error_add("%s'%s%d%s'%s", threshold_value
,
2698 op
, hilite
.value
.a_int
, percent
? "%" : "",
2701 } else if (dt
== ANY_LONG
2702 && hilite
.value
.a_long
< (grt
? -1L : lt
? 1L : 0L)) {
2703 config_error_add("%s'%s%ld'%s", threshold_value
,
2704 op
, hilite
.value
.a_long
, is_out_of_range
);
2707 } else if (initblstats
[fld
].anytype
== ANY_STR
) {
2711 config_error_add(has_ltgt_percentnumber(s
[sidx
])
2712 ? "Wrong format '%s', expected a threshold number or percent"
2713 : "Unknown behavior '%s'",
2718 /* relationships {LT_VALUE, LE_VALUE, EQ_VALUE, GE_VALUE, GT_VALUE} */
2720 hilite
.rel
= GT_VALUE
;
2721 else if (lt
|| down
)
2722 hilite
.rel
= LT_VALUE
;
2724 hilite
.rel
= GE_VALUE
;
2726 hilite
.rel
= LE_VALUE
;
2727 else if (eq
|| percent
|| numeric
|| changed
)
2728 hilite
.rel
= EQ_VALUE
;
2730 hilite
.rel
= TXT_VALUE
;
2732 hilite
.rel
= LT_VALUE
;
2734 if (initblstats
[fld
].anytype
== ANY_STR
&& (percent
|| numeric
)) {
2735 config_error_add("Field '%s' does not support numeric values",
2736 initblstats
[fld
].fldname
);
2741 if (initblstats
[fld
].idxmax
< 0) {
2742 config_error_add("Cannot use percent with '%s'",
2743 initblstats
[fld
].fldname
);
2745 } else if ((hilite
.value
.a_int
< -1)
2746 || (hilite
.value
.a_int
== -1
2747 && hilite
.value
.a_int
!= GT_VALUE
)
2748 || (hilite
.value
.a_int
== 0
2749 && hilite
.rel
== LT_VALUE
)
2750 || (hilite
.value
.a_int
== 100
2751 && hilite
.rel
== GT_VALUE
)
2752 || (hilite
.value
.a_int
== 101
2753 && hilite
.value
.a_int
!= LT_VALUE
)
2754 || (hilite
.value
.a_int
> 101)) {
2756 "hilite_status: invalid percentage value '%s%d%%'",
2757 (hilite
.rel
== LT_VALUE
) ? "<"
2758 : (hilite
.rel
== LE_VALUE
) ? "<="
2759 : (hilite
.rel
== GT_VALUE
) ? ">"
2760 : (hilite
.rel
== GE_VALUE
) ? ">="
2762 hilite
.value
.a_int
);
2776 sf
= splitsubfields(buf
, &subfields
, 0);
2781 disp_attrib
= HL_UNDEF
;
2783 for (i
= 0; i
< sf
; ++i
) {
2784 int a
= match_str2attr(subfields
[i
], FALSE
);
2787 disp_attrib
|= HL_BOLD
;
2788 else if (a
== ATR_DIM
)
2789 disp_attrib
|= HL_DIM
;
2790 else if (a
== ATR_ITALIC
)
2791 disp_attrib
|= HL_ITALIC
;
2792 else if (a
== ATR_ULINE
)
2793 disp_attrib
|= HL_ULINE
;
2794 else if (a
== ATR_BLINK
)
2795 disp_attrib
|= HL_BLINK
;
2796 else if (a
== ATR_INVERSE
)
2797 disp_attrib
|= HL_INVERSE
;
2798 else if (a
== ATR_NONE
)
2799 disp_attrib
= HL_NONE
;
2801 int c
= match_str2clr(subfields
[i
], FALSE
);
2803 if (c
>= CLR_MAX
|| coloridx
!= -1) {
2804 config_error_add("bad color '%d %d'", c
, coloridx
);
2811 coloridx
= NO_COLOR
;
2813 /* Assign the values */
2814 hilite
.coloridx
= coloridx
| (disp_attrib
<< 8);
2817 hilite
.behavior
= BL_TH_ALWAYS_HILITE
;
2819 hilite
.behavior
= BL_TH_VAL_PERCENTAGE
;
2821 hilite
.behavior
= BL_TH_UPDOWN
;
2823 hilite
.behavior
= BL_TH_VAL_ABSOLUTE
;
2825 hilite
.behavior
= BL_TH_TEXTMATCH
;
2826 else if (hilite
.value
.a_void
)
2827 hilite
.behavior
= BL_TH_VAL_ABSOLUTE
;
2828 else if (criticalhp
)
2829 hilite
.behavior
= BL_TH_CRITICALHP
;
2831 hilite
.behavior
= BL_TH_NONE
;
2833 hilite
.anytype
= dt
;
2835 if (hilite
.behavior
== BL_TH_TEXTMATCH
&& txt
) {
2836 (void) strncpy(hilite
.textmatch
, txt
, sizeof hilite
.textmatch
);
2837 hilite
.textmatch
[sizeof hilite
.textmatch
- 1] = '\0';
2838 (void) trimspaces(hilite
.textmatch
);
2841 status_hilite_add_threshold(fld
, &hilite
);
2847 return (successes
> 0);
2850 staticfn
unsigned long
2851 query_conditions(void)
2854 unsigned long ret
= 0UL;
2857 menu_item
*picks
= (menu_item
*) 0;
2860 tmpwin
= create_nhwindow(NHW_MENU
);
2861 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
2863 for (i
= 0; i
< SIZE(conditions
); i
++) {
2865 any
.a_ulong
= conditions
[i
].mask
;
2866 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
2867 clr
, conditions
[i
].text
[0], MENU_ITEMFLAGS_NONE
);
2870 end_menu(tmpwin
, "Choose status conditions");
2872 res
= select_menu(tmpwin
, PICK_ANY
, &picks
);
2873 destroy_nhwindow(tmpwin
);
2875 for (i
= 0; i
< res
; i
++)
2876 ret
|= picks
[i
].item
.a_ulong
;
2877 free((genericptr_t
) picks
);
2883 conditionbitmask2str(unsigned long ul
)
2885 static char buf
[BUFSZ
];
2887 boolean first
= TRUE
;
2888 const char *alias
= (char *) 0;
2895 for (i
= 1; i
< SIZE(condition_aliases
); i
++)
2896 if (condition_aliases
[i
].bitmask
== ul
)
2897 alias
= condition_aliases
[i
].id
;
2899 for (i
= 0; i
< SIZE(conditions
); i
++)
2900 if ((conditions
[i
].mask
& ul
) != 0UL) {
2901 Sprintf(eos(buf
), "%s%s", (first
) ? "" : "+",
2902 conditions
[i
].text
[0]);
2906 if (!first
&& alias
)
2907 Sprintf(buf
, "%s", alias
);
2912 staticfn
unsigned long
2913 match_str2conditionbitmask(const char *str
)
2915 int i
, nmatches
= 0;
2916 unsigned long mask
= 0UL;
2919 /* check matches to canonical names */
2920 for (i
= 0; i
< SIZE(conditions
); i
++)
2921 if (fuzzymatch(conditions
[i
].text
[0], str
, " -_", TRUE
)) {
2922 mask
|= conditions
[i
].mask
;
2928 for (i
= 0; i
< SIZE(condition_aliases
); i
++)
2929 if (fuzzymatch(condition_aliases
[i
].id
, str
, " -_", TRUE
)) {
2930 mask
|= condition_aliases
[i
].bitmask
;
2936 /* check partial matches to aliases */
2937 int len
= (int) strlen(str
);
2939 for (i
= 0; i
< SIZE(condition_aliases
); i
++)
2940 if (!strncmpi(str
, condition_aliases
[i
].id
, len
)) {
2941 mask
|= condition_aliases
[i
].bitmask
;
2950 staticfn
unsigned long
2951 str2conditionbitmask(char *str
)
2953 unsigned long conditions_bitmask
= 0UL;
2957 sf
= splitsubfields(str
, &subfields
, SIZE(conditions
));
2962 for (i
= 0; i
< sf
; ++i
) {
2963 unsigned long bm
= match_str2conditionbitmask(subfields
[i
]);
2966 config_error_add("Unknown condition '%s'", subfields
[i
]);
2969 conditions_bitmask
|= bm
;
2971 return conditions_bitmask
;
2975 parse_condition(char (*s
)[QBUFSZ
], int sidx
)
2978 int coloridx
= NO_COLOR
;
2980 unsigned long conditions_bitmask
= 0UL;
2981 boolean result
= FALSE
;
2987 OPTION=hilite_status: condition/stone+slime+foodPois/red&inverse */
2991 * It would be simpler to treat each condition (also hunger state
2992 * and encumbrance level) as if it were a separate field. That
2993 * way they could have either or both 'changed' temporary rule and
2994 * 'always' persistent rule and wouldn't need convoluted access to
2995 * the intended color and attributes.
3000 config_error_add("Missing condition(s)");
3003 while (s
[sidx
][0]) {
3004 int sf
= 0; /* subfield count */
3005 char buf
[BUFSZ
], **subfields
;
3009 conditions_bitmask
= str2conditionbitmask(buf
);
3011 if (!conditions_bitmask
)
3015 * We have the conditions_bitmask with bits set for
3016 * each ailment we want in a particular color and/or
3017 * attribute, but we need to assign it to an array of
3018 * bitmasks indexed by the color chosen
3019 * (0 to (CLR_MAX - 1))
3020 * and/or attributes chosen
3021 * (HL_ATTCLR_NONE to (BL_ATTCLR_MAX - 1))
3022 * We still have to parse the colors and attributes out.
3028 if (!how
|| !*how
) {
3029 config_error_add("Missing color+attribute");
3034 sf
= splitsubfields(buf
, &subfields
, 0);
3037 * conditions_bitmask now has bits set representing
3038 * the conditions that player wants represented, but
3039 * now we parse out *how* they will be represented.
3041 * Only 1 colour is allowed, but potentially multiple
3042 * attributes are allowed.
3044 * We have the following additional array offsets to
3045 * use for storing the attributes beyond the end of
3046 * the color indexes, all of which are less than CLR_MAX.
3050 for (i
= 0; i
< sf
; ++i
) {
3051 int a
= match_str2attr(subfields
[i
], FALSE
);
3054 gc
.cond_hilites
[HL_ATTCLR_BOLD
] |= conditions_bitmask
;
3055 else if (a
== ATR_DIM
)
3056 gc
.cond_hilites
[HL_ATTCLR_DIM
] |= conditions_bitmask
;
3057 else if (a
== ATR_ITALIC
)
3058 gc
.cond_hilites
[HL_ATTCLR_ITALIC
] |= conditions_bitmask
;
3059 else if (a
== ATR_ULINE
)
3060 gc
.cond_hilites
[HL_ATTCLR_ULINE
] |= conditions_bitmask
;
3061 else if (a
== ATR_BLINK
)
3062 gc
.cond_hilites
[HL_ATTCLR_BLINK
] |= conditions_bitmask
;
3063 else if (a
== ATR_INVERSE
)
3064 gc
.cond_hilites
[HL_ATTCLR_INVERSE
] |= conditions_bitmask
;
3065 else if (a
== ATR_NONE
) {
3066 gc
.cond_hilites
[HL_ATTCLR_BOLD
] &= ~conditions_bitmask
;
3067 gc
.cond_hilites
[HL_ATTCLR_DIM
] &= ~conditions_bitmask
;
3068 gc
.cond_hilites
[HL_ATTCLR_ITALIC
] &= ~conditions_bitmask
;
3069 gc
.cond_hilites
[HL_ATTCLR_ULINE
] &= ~conditions_bitmask
;
3070 gc
.cond_hilites
[HL_ATTCLR_BLINK
] &= ~conditions_bitmask
;
3071 gc
.cond_hilites
[HL_ATTCLR_INVERSE
] &= ~conditions_bitmask
;
3073 int k
= match_str2clr(subfields
[i
], FALSE
);
3076 config_error_add("bad color %d", k
);
3082 /* set the bits in the appropriate member of the
3083 condition array according to color chosen as index */
3085 gc
.cond_hilites
[coloridx
] |= conditions_bitmask
;
3093 clear_status_hilites(void)
3097 for (i
= 0; i
< MAXBLSTATS
; ++i
) {
3098 struct hilite_s
*temp
, *next
;
3100 for (temp
= gb
.blstats
[0][i
].thresholds
; temp
; temp
= next
) {
3104 gb
.blstats
[0][i
].thresholds
= gb
.blstats
[1][i
].thresholds
= 0;
3105 /* pointer into thresholds list, now stale */
3106 gb
.blstats
[0][i
].hilite_rule
= gb
.blstats
[1][i
].hilite_rule
= 0;
3111 hlattr2attrname(int attrib
, char *buf
, size_t bufsz
)
3113 if (attrib
&& buf
) {
3119 if (attrib
== HL_NONE
) {
3120 Strcpy(buf
, "normal");
3124 if (attrib
& HL_BOLD
)
3125 Strcat(attbuf
, first
++ ? "+bold" : "bold");
3126 if (attrib
& HL_DIM
)
3127 Strcat(attbuf
, first
++ ? "+dim" : "dim");
3128 if (attrib
& HL_ITALIC
)
3129 Strcat(attbuf
, first
++ ? "+italic" : "italic");
3130 if (attrib
& HL_ULINE
)
3131 Strcat(attbuf
, first
++ ? "+underline" : "underline");
3132 if (attrib
& HL_BLINK
)
3133 Strcat(attbuf
, first
++ ? "+blink" : "blink");
3134 if (attrib
& HL_INVERSE
)
3135 Strcat(attbuf
, first
++ ? "+inverse" : "inverse");
3138 if (k
< (size_t)(bufsz
- 1))
3139 Strcpy(buf
, attbuf
);
3145 struct _status_hilite_line_str
{
3148 struct hilite_s
*hl
;
3151 struct _status_hilite_line_str
*next
;
3154 /* these don't need to be in 'struct g' */
3155 static struct _status_hilite_line_str
*status_hilite_str
= 0;
3156 static int status_hilite_str_id
= 0;
3159 status_hilite_linestr_add(
3161 struct hilite_s
*hl
,
3165 struct _status_hilite_line_str
*tmp
, *nxt
;
3167 tmp
= (struct _status_hilite_line_str
*) alloc(sizeof *tmp
);
3168 (void) memset(tmp
, 0, sizeof *tmp
);
3169 tmp
->next
= (struct _status_hilite_line_str
*) 0;
3171 tmp
->id
= ++status_hilite_str_id
;
3175 if (fld
== BL_TITLE
)
3176 Strcpy(tmp
->str
, str
);
3178 (void) stripchars(tmp
->str
, " ", str
);
3180 if ((nxt
= status_hilite_str
) != 0) {
3185 status_hilite_str
= tmp
;
3190 status_hilite_linestr_done(void)
3192 struct _status_hilite_line_str
*nxt
, *tmp
= status_hilite_str
;
3199 status_hilite_str
= (struct _status_hilite_line_str
*) 0;
3200 status_hilite_str_id
= 0;
3204 status_hilite_linestr_countfield(int fld
)
3206 struct _status_hilite_line_str
*tmp
;
3207 boolean countall
= (fld
== BL_FLUSH
);
3210 for (tmp
= status_hilite_str
; tmp
; tmp
= tmp
->next
) {
3211 if (countall
|| tmp
->fld
== fld
)
3217 /* used by options handling, doset(options.c) */
3219 count_status_hilites(void)
3223 status_hilite_linestr_gather();
3224 count
= status_hilite_linestr_countfield(BL_FLUSH
);
3225 status_hilite_linestr_done();
3230 status_hilite_linestr_gather_conditions(void)
3235 unsigned int clratr
;
3236 } cond_maps
[SIZE(conditions
)];
3238 (void) memset(cond_maps
, 0,
3239 SIZE(conditions
) * sizeof (struct _cond_map
));
3241 for (i
= 0; i
< SIZE(conditions
); i
++) {
3246 for (j
= 0; j
< CLR_MAX
; j
++)
3247 if (gc
.cond_hilites
[j
] & conditions
[i
].mask
) {
3251 if (gc
.cond_hilites
[HL_ATTCLR_BOLD
] & conditions
[i
].mask
)
3253 if (gc
.cond_hilites
[HL_ATTCLR_DIM
] & conditions
[i
].mask
)
3255 if (gc
.cond_hilites
[HL_ATTCLR_ITALIC
] & conditions
[i
].mask
)
3257 if (gc
.cond_hilites
[HL_ATTCLR_ULINE
] & conditions
[i
].mask
)
3259 if (gc
.cond_hilites
[HL_ATTCLR_BLINK
] & conditions
[i
].mask
)
3261 if (gc
.cond_hilites
[HL_ATTCLR_INVERSE
] & conditions
[i
].mask
)
3266 if (clr
!= NO_COLOR
|| atr
!= HL_NONE
) {
3267 unsigned int ca
= clr
| (atr
<< 8);
3268 boolean added_condmap
= FALSE
;
3270 for (j
= 0; j
< SIZE(conditions
); j
++)
3271 if (cond_maps
[j
].clratr
== ca
) {
3272 cond_maps
[j
].bm
|= conditions
[i
].mask
;
3273 added_condmap
= TRUE
;
3276 if (!added_condmap
) {
3277 for (j
= 0; j
< SIZE(conditions
); j
++)
3278 if (!cond_maps
[j
].bm
) {
3279 cond_maps
[j
].bm
= conditions
[i
].mask
;
3280 cond_maps
[j
].clratr
= ca
;
3287 for (i
= 0; i
< SIZE(conditions
); i
++)
3288 if (cond_maps
[i
].bm
) {
3289 int clr
= NO_COLOR
, atr
= HL_NONE
;
3291 split_clridx(cond_maps
[i
].clratr
, &clr
, &atr
);
3292 if (clr
!= NO_COLOR
|| atr
!= HL_NONE
) {
3294 char attrbuf
[BUFSZ
];
3295 char condbuf
[BUFSZ
];
3298 (void) strNsubst(strcpy(clrbuf
, clr2colorname(clr
)),
3300 tmpattr
= hlattr2attrname(atr
, attrbuf
, BUFSZ
);
3302 Sprintf(eos(clrbuf
), "&%s", tmpattr
);
3303 Snprintf(condbuf
, sizeof(condbuf
), "condition/%s/%s",
3304 conditionbitmask2str(cond_maps
[i
].bm
), clrbuf
);
3305 status_hilite_linestr_add(BL_CONDITION
, 0,
3306 cond_maps
[i
].bm
, condbuf
);
3312 status_hilite_linestr_gather(void)
3315 struct hilite_s
*hl
;
3317 status_hilite_linestr_done();
3319 for (i
= 0; i
< MAXBLSTATS
; i
++) {
3320 hl
= gb
.blstats
[0][i
].thresholds
;
3322 status_hilite_linestr_add(i
, hl
, 0UL, status_hilite2str(hl
));
3327 status_hilite_linestr_gather_conditions();
3332 status_hilite2str(struct hilite_s
*hl
)
3334 static char buf
[BUFSZ
];
3335 int clr
= NO_COLOR
, attr
= ATR_NONE
;
3336 char behavebuf
[BUFSZ
];
3338 char attrbuf
[BUFSZ
];
3345 behavebuf
[0] = '\0';
3347 op
= (hl
->rel
== LT_VALUE
) ? "<"
3348 : (hl
->rel
== LE_VALUE
) ? "<="
3349 : (hl
->rel
== GT_VALUE
) ? ">"
3350 : (hl
->rel
== GE_VALUE
) ? ">="
3351 : (hl
->rel
== EQ_VALUE
) ? "="
3354 switch (hl
->behavior
) {
3355 case BL_TH_VAL_PERCENTAGE
:
3357 Sprintf(behavebuf
, "%s%d%%", op
, hl
->value
.a_int
);
3359 impossible("hl->behavior=percentage, rel error");
3362 if (hl
->rel
== LT_VALUE
)
3363 Sprintf(behavebuf
, "down");
3364 else if (hl
->rel
== GT_VALUE
)
3365 Sprintf(behavebuf
, "up");
3366 else if (hl
->rel
== EQ_VALUE
)
3367 Sprintf(behavebuf
, "changed");
3369 impossible("hl->behavior=updown, rel error");
3371 case BL_TH_VAL_ABSOLUTE
:
3373 Sprintf(behavebuf
, "%s%d", op
, hl
->value
.a_int
);
3375 impossible("hl->behavior=absolute, rel error");
3377 case BL_TH_TEXTMATCH
:
3378 if (hl
->rel
== TXT_VALUE
&& hl
->textmatch
[0])
3379 Sprintf(behavebuf
, "%s", hl
->textmatch
);
3381 impossible("hl->behavior=textmatch, rel or textmatch error");
3383 case BL_TH_CONDITION
:
3384 if (hl
->rel
== EQ_VALUE
)
3385 Sprintf(behavebuf
, "%s", conditionbitmask2str(hl
->value
.a_ulong
));
3387 impossible("hl->behavior=condition, rel error");
3389 case BL_TH_ALWAYS_HILITE
:
3390 Sprintf(behavebuf
, "always");
3392 case BL_TH_CRITICALHP
:
3393 Sprintf(behavebuf
, "criticalhp");
3401 split_clridx(hl
->coloridx
, &clr
, &attr
);
3402 (void) strNsubst(strcpy(clrbuf
, clr2colorname(clr
)), " ", "-", 0);
3403 if (attr
!= HL_UNDEF
) {
3404 if ((tmpattr
= hlattr2attrname(attr
, attrbuf
, BUFSZ
)) != 0)
3405 Sprintf(eos(clrbuf
), "&%s", tmpattr
);
3407 Snprintf(buf
, sizeof(buf
), "%s/%s/%s", initblstats
[hl
->fld
].fldname
,
3414 status_hilite_menu_choose_field(void)
3417 int i
, res
, fld
= BL_FLUSH
;
3419 menu_item
*picks
= (menu_item
*) 0;
3422 tmpwin
= create_nhwindow(NHW_MENU
);
3423 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
3425 for (i
= 0; i
< MAXBLSTATS
; i
++) {
3426 #ifndef SCORE_ON_BOTL
3427 if (initblstats
[i
].fld
== BL_SCORE
3428 && !gb
.blstats
[0][BL_SCORE
].thresholds
)
3432 any
.a_int
= (i
+ 1);
3433 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
3434 clr
, initblstats
[i
].fldname
, MENU_ITEMFLAGS_NONE
);
3437 end_menu(tmpwin
, "Select a hilite field:");
3439 res
= select_menu(tmpwin
, PICK_ONE
, &picks
);
3440 destroy_nhwindow(tmpwin
);
3442 fld
= picks
->item
.a_int
- 1;
3443 free((genericptr_t
) picks
);
3449 status_hilite_menu_choose_behavior(int fld
)
3452 int res
= 0, beh
= BL_TH_NONE
-1;
3454 menu_item
*picks
= (menu_item
*) 0;
3457 int onlybeh
= BL_TH_NONE
, nopts
= 0;
3460 if (fld
< 0 || fld
>= MAXBLSTATS
)
3463 at
= initblstats
[fld
].anytype
;
3465 tmpwin
= create_nhwindow(NHW_MENU
);
3466 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
3468 if (fld
!= BL_CONDITION
) {
3470 any
.a_int
= onlybeh
= BL_TH_ALWAYS_HILITE
;
3471 Sprintf(buf
, "Always highlight %s", initblstats
[fld
].fldname
);
3472 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'a', 0, ATR_NONE
,
3473 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3477 if (fld
== BL_CONDITION
) {
3479 any
.a_int
= onlybeh
= BL_TH_CONDITION
;
3480 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'b', 0, ATR_NONE
,
3481 clr
, "Bitmask of conditions", MENU_ITEMFLAGS_NONE
);
3485 if (fld
!= BL_CONDITION
&& fld
!= BL_VERS
) {
3487 any
.a_int
= onlybeh
= BL_TH_UPDOWN
;
3488 Sprintf(buf
, "%s value changes", initblstats
[fld
].fldname
);
3489 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'c', 0, ATR_NONE
,
3490 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3494 if (fld
!= BL_CAP
&& fld
!= BL_HUNGER
3495 && (at
== ANY_INT
|| at
== ANY_LONG
)) {
3497 any
.a_int
= onlybeh
= BL_TH_VAL_ABSOLUTE
;
3498 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'n', 0, ATR_NONE
,
3499 clr
, "Number threshold", MENU_ITEMFLAGS_NONE
);
3503 if (initblstats
[fld
].idxmax
>= 0) {
3505 any
.a_int
= onlybeh
= BL_TH_VAL_PERCENTAGE
;
3506 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'p', 0, ATR_NONE
,
3507 clr
, "Percentage threshold", MENU_ITEMFLAGS_NONE
);
3513 any
.a_int
= onlybeh
= BL_TH_CRITICALHP
;
3514 Sprintf(buf
, "Highlight critically low %s",
3515 initblstats
[fld
].fldname
);
3516 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'C', 0, ATR_NONE
,
3517 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3521 if (initblstats
[fld
].anytype
== ANY_STR
3522 || fld
== BL_CAP
|| fld
== BL_HUNGER
) {
3524 any
.a_int
= onlybeh
= BL_TH_TEXTMATCH
;
3525 Sprintf(buf
, "%s text match", initblstats
[fld
].fldname
);
3526 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 't', 0, ATR_NONE
,
3527 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3531 Sprintf(buf
, "Select %s field hilite behavior:",
3532 initblstats
[fld
].fldname
);
3533 end_menu(tmpwin
, buf
);
3536 res
= select_menu(tmpwin
, PICK_ONE
, &picks
);
3537 if (res
== 0) /* none chosen*/
3539 else if (res
== -1) /* menu cancelled */
3540 beh
= (BL_TH_NONE
- 1);
3541 } else if (onlybeh
!= BL_TH_NONE
) {
3544 destroy_nhwindow(tmpwin
);
3546 beh
= picks
->item
.a_int
;
3547 free((genericptr_t
) picks
);
3553 status_hilite_menu_choose_updownboth(
3556 boolean ltok
, boolean gtok
)
3558 int res
, ret
= NO_LTEQGT
;
3562 menu_item
*picks
= (menu_item
*) 0;
3565 tmpwin
= create_nhwindow(NHW_MENU
);
3566 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
3570 Sprintf(buf
, "%s than %s",
3571 (fld
== BL_AC
) ? "Better (lower)" : "Less", str
);
3573 Sprintf(buf
, "Value goes down");
3575 any
.a_int
= 10 + LT_VALUE
;
3576 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
3577 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3580 Sprintf(buf
, "%s or %s",
3581 str
, (fld
== BL_AC
) ? "better (lower)" : "less");
3583 any
.a_int
= 10 + LE_VALUE
;
3584 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
3585 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3590 Sprintf(buf
, "Exactly %s", str
);
3592 Sprintf(buf
, "Value changes");
3594 any
.a_int
= 10 + EQ_VALUE
;
3595 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
3596 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3600 Sprintf(buf
, "%s or %s",
3601 str
, (fld
== BL_AC
) ? "worse (higher)" : "more");
3603 any
.a_int
= 10 + GE_VALUE
;
3604 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
3605 clr
, buf
, MENU_ITEMFLAGS_NONE
);
3609 Sprintf(buf
, "%s than %s",
3610 (fld
== BL_AC
) ? "Worse (higher)" : "More", str
);
3612 Sprintf(buf
, "Value goes up");
3614 any
.a_int
= 10 + GT_VALUE
;
3615 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
, clr
,
3616 buf
, MENU_ITEMFLAGS_NONE
);
3618 Sprintf(buf
, "Select field %s value:", initblstats
[fld
].fldname
);
3619 end_menu(tmpwin
, buf
);
3621 res
= select_menu(tmpwin
, PICK_ONE
, &picks
);
3622 destroy_nhwindow(tmpwin
);
3624 ret
= picks
->item
.a_int
- 10;
3625 free((genericptr_t
) picks
);
3632 status_hilite_menu_add(int origfld
)
3637 int clr
= NO_COLOR
, atr
= HL_UNDEF
;
3638 struct hilite_s hilite
;
3639 unsigned long cond
= 0UL;
3640 char colorqry
[BUFSZ
];
3641 char attrqry
[BUFSZ
];
3645 if (fld
== BL_FLUSH
) {
3646 fld
= status_hilite_menu_choose_field();
3647 /* isn't this redundant given what follows? */
3648 if (fld
== BL_FLUSH
)
3652 if (fld
== BL_FLUSH
)
3658 memset((genericptr_t
) &hilite
, 0, sizeof (struct hilite_s
));
3659 hilite
.next
= (struct hilite_s
*) 0;
3660 hilite
.set
= FALSE
; /* mark it "unset" */
3664 behavior
= status_hilite_menu_choose_behavior(fld
);
3666 if (behavior
== (BL_TH_NONE
- 1)) {
3668 } else if (behavior
== BL_TH_NONE
) {
3669 if (origfld
== BL_FLUSH
)
3674 hilite
.behavior
= behavior
;
3677 if (behavior
== BL_TH_VAL_PERCENTAGE
3678 || behavior
== BL_TH_VAL_ABSOLUTE
) {
3679 char inbuf
[BUFSZ
], buf
[BUFSZ
];
3682 boolean gotnum
= FALSE
, percent
= (behavior
== BL_TH_VAL_PERCENTAGE
);
3683 char *inp
, *numstart
;
3686 lt_gt_eq
= NO_LTEQGT
; /* not set up yet */
3688 Sprintf(buf
, "Enter %svalue for %s threshold:",
3689 percent
? "percentage " : "",
3690 initblstats
[fld
].fldname
);
3692 if (inbuf
[0] == '\0' || inbuf
[0] == '\033')
3693 goto choose_behavior
;
3695 inp
= numstart
= trimspaces(inbuf
);
3697 goto choose_behavior
;
3699 /* allow user to enter "<50%" or ">50" or just "50"
3700 or <=50% or >=50 or =50 */
3701 if (*inp
== '>' || *inp
== '<' || *inp
== '=') {
3702 lt_gt_eq
= (*inp
== '>') ? ((inp
[1] == '=') ? GE_VALUE
: GT_VALUE
)
3703 : (*inp
== '<') ? ((inp
[1] == '=') ? LE_VALUE
: LT_VALUE
)
3707 if (lt_gt_eq
== GE_VALUE
|| lt_gt_eq
== LE_VALUE
) {
3714 } else if (*inp
== '+') {
3718 while (digit(*inp
)) {
3724 pline("Not expecting a percentage.");
3725 goto choose_behavior
;
3727 *inp
= '\0'; /* strip '%' [this accepts trailing junk!] */
3729 /* some random characters */
3730 pline("\"%s\" is not a recognized number.", inp
);
3734 pline("Is that an invisible number?");
3737 op
= (lt_gt_eq
== LT_VALUE
) ? "<"
3738 : (lt_gt_eq
== LE_VALUE
) ? "<="
3739 : (lt_gt_eq
== GT_VALUE
) ? ">"
3740 : (lt_gt_eq
== GE_VALUE
) ? ">="
3741 : (lt_gt_eq
== EQ_VALUE
) ? "="
3742 : ""; /* didn't specify lt_gt_eq with number */
3745 dt
= percent
? ANY_INT
: initblstats
[fld
].anytype
;
3746 (void) s_to_anything(&aval
, numstart
, dt
);
3750 if (initblstats
[fld
].idxmax
== -1) {
3751 pline("Field '%s' does not support percentage values.",
3752 initblstats
[fld
].fldname
);
3753 behavior
= BL_TH_VAL_ABSOLUTE
;
3756 /* if player only specified a number then lt_gt_eq isn't set
3757 up yet and the >-1 and <101 exceptions can't be honored;
3758 deliberate use of those should be uncommon enough for
3759 that to be palatable; for 0 and 100, choose_updown_both()
3760 will prevent useless operations */
3761 if ((val
< 0 && (val
!= -1 || lt_gt_eq
!= GT_VALUE
))
3762 || (val
== 0 && lt_gt_eq
== LT_VALUE
)
3763 || (val
== 100 && lt_gt_eq
== GT_VALUE
)
3764 || (val
> 100 && (val
!= 101 || lt_gt_eq
!= LT_VALUE
))) {
3765 pline("'%s%d%%' is not a valid percent value.", op
, val
);
3768 /* restore suffix for use in color and attribute prompts */
3769 if (!strchr(numstart
, '%'))
3770 Strcat(numstart
, "%");
3772 /* reject negative values except for AC and >-1; reject 0 for < */
3773 } else if (dt
== ANY_INT
3774 && (aval
.a_int
< ((fld
== BL_AC
) ? -128
3775 : (lt_gt_eq
== GT_VALUE
) ? -1
3776 : (lt_gt_eq
== LT_VALUE
) ? 1 : 0))) {
3777 pline("%s'%s%d'%s", threshold_value
,
3778 op
, aval
.a_int
, is_out_of_range
);
3780 } else if (dt
== ANY_LONG
3781 && (aval
.a_long
< ((lt_gt_eq
== GT_VALUE
) ? -1L
3782 : (lt_gt_eq
== LT_VALUE
) ? 1L : 0L))) {
3783 pline("%s'%s%ld'%s", threshold_value
,
3784 op
, aval
.a_long
, is_out_of_range
);
3788 if (lt_gt_eq
== NO_LTEQGT
) {
3789 boolean ltok
= ((dt
== ANY_INT
)
3790 ? (aval
.a_int
> 0 || fld
== BL_AC
)
3791 : (aval
.a_long
> 0L)),
3792 gtok
= (!percent
|| aval
.a_long
< 100);
3794 lt_gt_eq
= status_hilite_menu_choose_updownboth(fld
, inbuf
,
3796 if (lt_gt_eq
== NO_LTEQGT
)
3800 Sprintf(colorqry
, "Choose a color for when %s is %s%s%s:",
3801 initblstats
[fld
].fldname
,
3802 (lt_gt_eq
== LT_VALUE
) ? "less than "
3803 : (lt_gt_eq
== GT_VALUE
) ? "more than "
3806 (lt_gt_eq
== LE_VALUE
) ? " or less"
3807 : (lt_gt_eq
== GE_VALUE
) ? " or more"
3809 Sprintf(attrqry
, "Choose attribute for when %s is %s%s%s:",
3810 initblstats
[fld
].fldname
,
3811 (lt_gt_eq
== LT_VALUE
) ? "less than "
3812 : (lt_gt_eq
== GT_VALUE
) ? "more than "
3815 (lt_gt_eq
== LE_VALUE
) ? " or less"
3816 : (lt_gt_eq
== GE_VALUE
) ? " or more"
3819 hilite
.rel
= lt_gt_eq
;
3820 hilite
.value
= aval
;
3821 } else if (behavior
== BL_TH_UPDOWN
) {
3822 if (initblstats
[fld
].anytype
!= ANY_STR
) {
3823 boolean ltok
= (fld
!= BL_TIME
), gtok
= TRUE
;
3825 lt_gt_eq
= status_hilite_menu_choose_updownboth(fld
, (char *) 0,
3827 if (lt_gt_eq
== NO_LTEQGT
)
3828 goto choose_behavior
;
3829 } else { /* ANY_STR */
3830 /* player picked '<field> value changes' in outer menu;
3831 ordered string comparison is supported but LT/GT for the
3832 string status fields (title, dungeon level, alignment)
3833 is pointless; rather than calling ..._choose_updownboth()
3834 with ltok==False plus gtok=False and having a menu with a
3835 single choice, skip it altogether and just use 'changed' */
3836 lt_gt_eq
= EQ_VALUE
;
3838 Sprintf(colorqry
, "Choose a color for when %s %s:",
3839 initblstats
[fld
].fldname
,
3840 (lt_gt_eq
== EQ_VALUE
) ? "changes"
3841 : (lt_gt_eq
== LT_VALUE
) ? "decreases"
3843 Sprintf(attrqry
, "Choose attribute for when %s %s:",
3844 initblstats
[fld
].fldname
,
3845 (lt_gt_eq
== EQ_VALUE
) ? "changes"
3846 : (lt_gt_eq
== LT_VALUE
) ? "decreases"
3848 hilite
.rel
= lt_gt_eq
;
3849 } else if (behavior
== BL_TH_CONDITION
) {
3850 cond
= query_conditions();
3852 if (origfld
== BL_FLUSH
)
3856 Snprintf(colorqry
, sizeof(colorqry
),
3857 "Choose a color for conditions %s:",
3858 conditionbitmask2str(cond
));
3859 Snprintf(attrqry
, sizeof(attrqry
),
3860 "Choose attribute for conditions %s:",
3861 conditionbitmask2str(cond
));
3862 } else if (behavior
== BL_TH_TEXTMATCH
) {
3863 char qry_buf
[BUFSZ
];
3865 Sprintf(qry_buf
, "%s %s text value to match:",
3869 || fld
== BL_TITLE
) ? "Choose" : "Enter",
3870 initblstats
[fld
].fldname
);
3871 if (fld
== BL_CAP
) {
3872 int rv
= query_arrayvalue(qry_buf
,
3874 SLT_ENCUMBER
, OVERLOADED
+ 1);
3876 if (rv
< SLT_ENCUMBER
)
3877 goto choose_behavior
;
3879 hilite
.rel
= TXT_VALUE
;
3880 Strcpy(hilite
.textmatch
, enc_stat
[rv
]);
3881 } else if (fld
== BL_ALIGN
) {
3882 static const char *const aligntxt
[] = {
3883 "chaotic", "neutral", "lawful"
3885 int rv
= query_arrayvalue(qry_buf
,
3886 aligntxt
, 0, 2 + 1);
3889 goto choose_behavior
;
3891 hilite
.rel
= TXT_VALUE
;
3892 Strcpy(hilite
.textmatch
, aligntxt
[rv
]);
3893 } else if (fld
== BL_HUNGER
) {
3894 static const char *const hutxt
[] = {
3895 "Satiated", (char *) 0, "Hungry", "Weak",
3896 "Fainting", "Fainted", "Starved"
3898 int rv
= query_arrayvalue(qry_buf
, hutxt
, SATIATED
, STARVED
+ 1);
3901 goto choose_behavior
;
3903 hilite
.rel
= TXT_VALUE
;
3904 Strcpy(hilite
.textmatch
, hutxt
[rv
]);
3905 } else if (fld
== BL_TITLE
) {
3906 const char *rolelist
[3 * 9 + 1];
3907 char mbuf
[MAXVALWIDTH
], fbuf
[MAXVALWIDTH
], obuf
[MAXVALWIDTH
];
3910 for (i
= j
= 0; i
< 9; i
++) {
3911 Sprintf(mbuf
, "\"%s\"", gu
.urole
.rank
[i
].m
);
3912 if (gu
.urole
.rank
[i
].f
) {
3913 Sprintf(fbuf
, "\"%s\"", gu
.urole
.rank
[i
].f
);
3914 Snprintf(obuf
, sizeof obuf
, "%s or %s",
3915 flags
.female
? fbuf
: mbuf
,
3916 flags
.female
? mbuf
: fbuf
);
3918 fbuf
[0] = obuf
[0] = '\0';
3922 rolelist
[j
++] = dupstr(fbuf
);
3923 rolelist
[j
++] = dupstr(mbuf
);
3925 rolelist
[j
++] = dupstr(obuf
);
3927 rolelist
[j
++] = dupstr(mbuf
);
3929 rolelist
[j
++] = dupstr(fbuf
);
3931 rolelist
[j
++] = dupstr(obuf
);
3934 rolelist
[j
++] = dupstr("\"none of the above (polymorphed)\"");
3936 rv
= query_arrayvalue(qry_buf
, rolelist
, 0, j
);
3938 hilite
.rel
= TXT_VALUE
;
3939 Strcpy(hilite
.textmatch
, rolelist
[rv
]);
3941 for (i
= 0; i
< j
; i
++)
3942 free((genericptr_t
) rolelist
[i
]), rolelist
[i
] = 0;
3944 goto choose_behavior
;
3949 getlin(qry_buf
, inbuf
);
3950 if (inbuf
[0] == '\0' || inbuf
[0] == '\033')
3951 goto choose_behavior
;
3953 hilite
.rel
= TXT_VALUE
;
3954 if (strlen(inbuf
) < sizeof hilite
.textmatch
)
3955 Strcpy(hilite
.textmatch
, inbuf
);
3959 Sprintf(colorqry
, "Choose a color for when %s is '%s':",
3960 initblstats
[fld
].fldname
, hilite
.textmatch
);
3961 Sprintf(attrqry
, "Choose attribute for when %s is '%s':",
3962 initblstats
[fld
].fldname
, hilite
.textmatch
);
3963 } else if (behavior
== BL_TH_ALWAYS_HILITE
) {
3964 Sprintf(colorqry
, "Choose a color to always hilite %s:",
3965 initblstats
[fld
].fldname
);
3966 Sprintf(attrqry
, "Choose attribute to always hilite %s:",
3967 initblstats
[fld
].fldname
);
3971 clr
= query_color(colorqry
, NO_COLOR
);
3973 if (behavior
!= BL_TH_ALWAYS_HILITE
)
3976 goto choose_behavior
;
3978 atr
= query_attr(attrqry
, ATR_NONE
);
3982 if (behavior
== BL_TH_CONDITION
) {
3984 char attrbuf
[BUFSZ
];
3988 gc
.cond_hilites
[HL_ATTCLR_BOLD
] |= cond
;
3990 gc
.cond_hilites
[HL_ATTCLR_DIM
] |= cond
;
3991 if (atr
& HL_ITALIC
)
3992 gc
.cond_hilites
[HL_ATTCLR_ITALIC
] |= cond
;
3994 gc
.cond_hilites
[HL_ATTCLR_ULINE
] |= cond
;
3996 gc
.cond_hilites
[HL_ATTCLR_BLINK
] |= cond
;
3997 if (atr
& HL_INVERSE
)
3998 gc
.cond_hilites
[HL_ATTCLR_INVERSE
] |= cond
;
3999 if (atr
== HL_NONE
) {
4000 gc
.cond_hilites
[HL_ATTCLR_BOLD
] &= ~cond
;
4001 gc
.cond_hilites
[HL_ATTCLR_DIM
] &= ~cond
;
4002 gc
.cond_hilites
[HL_ATTCLR_ITALIC
] &= ~cond
;
4003 gc
.cond_hilites
[HL_ATTCLR_ULINE
] &= ~cond
;
4004 gc
.cond_hilites
[HL_ATTCLR_BLINK
] &= ~cond
;
4005 gc
.cond_hilites
[HL_ATTCLR_INVERSE
] &= ~cond
;
4007 gc
.cond_hilites
[clr
] |= cond
;
4008 (void) strNsubst(strcpy(clrbuf
, clr2colorname(clr
)), " ", "-", 0);
4009 tmpattr
= hlattr2attrname(atr
, attrbuf
, BUFSZ
);
4011 Sprintf(eos(clrbuf
), "&%s", tmpattr
);
4012 pline("Added hilite condition/%s/%s",
4013 conditionbitmask2str(cond
), clrbuf
);
4017 hilite
.coloridx
= clr
| (atr
<< 8);
4018 hilite
.anytype
= initblstats
[fld
].anytype
;
4020 if (fld
== BL_TITLE
&& (p
= strstri(hilite
.textmatch
, " or ")) != 0) {
4021 /* split menu choice "male-rank or female-rank" into two distinct
4022 but otherwise identical rules, "male-rank" and "female-rank" */
4023 *p
= '\0'; /* chop off " or female-rank" */
4024 /* new rule for male-rank */
4025 status_hilite_add_threshold(fld
, &hilite
);
4026 pline("Added hilite %s", status_hilite2str(&hilite
));
4027 /* transfer female-rank to start of hilite.textmatch buffer */
4028 p
+= sizeof " or " - sizeof "";
4029 q
= hilite
.textmatch
;
4030 while ((*q
++ = *p
++) != '\0')
4032 /* proceed with normal addition of new rule */
4034 status_hilite_add_threshold(fld
, &hilite
);
4035 pline("Added hilite %s", status_hilite2str(&hilite
));
4037 reset_status_hilites();
4042 status_hilite_remove(int id
)
4044 struct _status_hilite_line_str
*hlstr
= status_hilite_str
;
4046 while (hlstr
&& hlstr
->id
!= id
) {
4047 hlstr
= hlstr
->next
;
4053 if (hlstr
->fld
== BL_CONDITION
) {
4056 for (i
= 0; i
< CLR_MAX
; i
++)
4057 gc
.cond_hilites
[i
] &= ~hlstr
->mask
;
4058 gc
.cond_hilites
[HL_ATTCLR_BOLD
] &= ~hlstr
->mask
;
4059 gc
.cond_hilites
[HL_ATTCLR_DIM
] &= ~hlstr
->mask
;
4060 gc
.cond_hilites
[HL_ATTCLR_ITALIC
] &= ~hlstr
->mask
;
4061 gc
.cond_hilites
[HL_ATTCLR_ULINE
] &= ~hlstr
->mask
;
4062 gc
.cond_hilites
[HL_ATTCLR_BLINK
] &= ~hlstr
->mask
;
4063 gc
.cond_hilites
[HL_ATTCLR_INVERSE
] &= ~hlstr
->mask
;
4066 int fld
= hlstr
->fld
;
4067 struct hilite_s
*hl
, *hlprev
= (struct hilite_s
*) 0;
4069 for (hl
= gb
.blstats
[0][fld
].thresholds
; hl
; hl
= hl
->next
) {
4070 if (hlstr
->hl
== hl
) {
4072 hlprev
->next
= hl
->next
;
4074 gb
.blstats
[0][fld
].thresholds
= hl
->next
;
4075 gb
.blstats
[1][fld
].thresholds
4076 = gb
.blstats
[0][fld
].thresholds
;
4078 if (gb
.blstats
[0][fld
].hilite_rule
== hl
) {
4079 gb
.blstats
[0][fld
].hilite_rule
4080 = gb
.blstats
[1][fld
].hilite_rule
4081 = (struct hilite_s
*) 0;
4082 gb
.blstats
[0][fld
].time
= gb
.blstats
[1][fld
].time
= 0L;
4084 free((genericptr_t
) hl
);
4094 status_hilite_menu_fld(int fld
)
4098 menu_item
*picks
= (menu_item
*) 0;
4100 int count
= status_hilite_linestr_countfield(fld
);
4101 struct _status_hilite_line_str
*hlstr
;
4107 if (status_hilite_menu_add(fld
)) {
4108 status_hilite_linestr_done();
4109 status_hilite_linestr_gather();
4110 count
= status_hilite_linestr_countfield(fld
);
4115 tmpwin
= create_nhwindow(NHW_MENU
);
4116 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
4119 hlstr
= status_hilite_str
;
4121 if (hlstr
->fld
== fld
) {
4123 any
.a_int
= hlstr
->id
;
4124 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
4125 clr
, hlstr
->str
, MENU_ITEMFLAGS_NONE
);
4127 hlstr
= hlstr
->next
;
4130 Sprintf(buf
, "No current hilites for %s", initblstats
[fld
].fldname
);
4131 add_menu_str(tmpwin
, buf
);
4134 /* separator line */
4135 add_menu_str(tmpwin
, "");
4140 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'X', 0, ATR_NONE
, clr
,
4141 "Remove selected hilites", MENU_ITEMFLAGS_NONE
);
4144 #ifndef SCORE_ON_BOTL
4145 if (fld
== BL_SCORE
) {
4146 /* suppress 'Z - Add a new hilite' for 'score' when SCORE_ON_BOTL
4147 is disabled; we wouldn't be called for 'score' unless it has
4148 hilite rules from the config file, so count must be positive
4149 (hence there's no risk that we're putting up an empty menu) */
4156 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 'Z', 0, ATR_NONE
,
4157 clr
, "Add new hilites", MENU_ITEMFLAGS_NONE
);
4160 Sprintf(buf
, "Current %s hilites:", initblstats
[fld
].fldname
);
4161 end_menu(tmpwin
, buf
);
4164 if ((res
= select_menu(tmpwin
, PICK_ANY
, &picks
)) > 0) {
4168 for (i
= 0; i
< res
; i
++) {
4169 idx
= picks
[i
].item
.a_int
;
4171 mode
|= 1; /* delete selected hilites */
4173 mode
|= 2; /* create new hilites */
4175 if (mode
& 1) { /* delete selected hilites */
4176 for (i
= 0; i
< res
; i
++) {
4177 idx
= picks
[i
].item
.a_int
;
4178 if (idx
> 0 && status_hilite_remove(idx
))
4182 if (mode
& 2) { /* create new hilites */
4183 while (status_hilite_menu_add(fld
))
4186 free((genericptr_t
) picks
), picks
= 0;
4188 destroy_nhwindow(tmpwin
);
4193 status_hilites_viewall(void)
4196 struct _status_hilite_line_str
*hlstr
= status_hilite_str
;
4199 datawin
= create_nhwindow(NHW_TEXT
);
4202 Sprintf(buf
, "OPTIONS=hilite_status: %.*s",
4203 (int) (BUFSZ
- sizeof "OPTIONS=hilite_status: " - 1),
4205 putstr(datawin
, 0, buf
);
4206 hlstr
= hlstr
->next
;
4209 display_nhwindow(datawin
, FALSE
);
4210 destroy_nhwindow(datawin
);
4214 all_options_statushilites(strbuf_t
*sbuf
)
4216 struct _status_hilite_line_str
*hlstr
;
4219 status_hilite_linestr_done();
4220 status_hilite_linestr_gather();
4222 hlstr
= status_hilite_str
;
4225 Sprintf(buf
, "OPTIONS=hilite_status: %.*s\n",
4226 (int) (BUFSZ
- sizeof "OPTIONS=hilite_status: " - 1),
4228 strbuf_append(sbuf
, buf
);
4229 hlstr
= hlstr
->next
;
4231 status_hilite_linestr_done();
4235 status_hilite_menu(void)
4239 menu_item
*picks
= (menu_item
*) 0;
4248 tmpwin
= create_nhwindow(NHW_MENU
);
4249 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
4251 status_hilite_linestr_gather();
4252 countall
= status_hilite_linestr_countfield(BL_FLUSH
);
4256 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
4257 clr
, "View all hilites in config format",
4258 MENU_ITEMFLAGS_NONE
);
4260 add_menu_str(tmpwin
, "");
4263 for (i
= 0; i
< MAXBLSTATS
; i
++) {
4267 fld
= initblstats
[i
].fld
;
4268 count
= status_hilite_linestr_countfield(fld
);
4269 #ifndef SCORE_ON_BOTL
4270 /* config file might contain rules for highlighting 'score'
4271 even when SCORE_ON_BOTL is disabled; if so, 'O' command
4272 menus will show them and allow deletions but not additions,
4273 otherwise, it won't show 'score' at all */
4274 if (fld
== BL_SCORE
&& !count
)
4278 any
.a_int
= fld
+ 1;
4279 Sprintf(buf
, "%-18s", initblstats
[i
].fldname
);
4281 Sprintf(eos(buf
), " (%d defined)", count
);
4282 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0, ATR_NONE
,
4283 clr
, buf
, MENU_ITEMFLAGS_NONE
);
4286 end_menu(tmpwin
, "Status hilites:");
4287 if ((res
= select_menu(tmpwin
, PICK_ONE
, &picks
)) > 0) {
4288 fld
= picks
->item
.a_int
- 1;
4290 status_hilites_viewall();
4292 if (status_hilite_menu_fld(fld
))
4293 reset_status_hilites();
4295 free((genericptr_t
) picks
), picks
= (menu_item
*) 0;
4299 destroy_nhwindow(tmpwin
);
4300 countall
= status_hilite_linestr_countfield(BL_FLUSH
);
4301 status_hilite_linestr_done();
4306 /* hilite_delta=='statushilites' does double duty: it is the
4307 number of turns for temporary highlights to remain visible
4308 and also when non-zero it is the flag to enable highlighting */
4309 if (countall
> 0 && !iflags
.hilite_delta
)
4310 iflags
.hilite_delta
= 3L;
4315 #endif /* STATUS_HILITES */