status_hilite for Xp and Exp by percent rules
Extend support for highlight rules that specify percentages from HP and spell power to experience level and experience points. For both of those, the percentage is based on progress from the start of the current Xp level to the start of the next Xp level. 100% isn't possible so is used to enable highlighting a special case: 1 point shy of next level, most likely to occur after losing a level. This is something I had in mind a long time ago and then forgot all about until fiddling with the final disclosure of experience points recently. It turned out to be trickier than expected because it needs to check whether Xp should have a status update when it hasn't changed but Exp has gone up. The latter might hit a percentage threshold that switches to another highlight rule. Fortunately changes to Exp, at least that aren't part of level gain or loss (which always trigger status updating), are all funnelled through a single place (I hope).
This commit is contained in:
134
src/botl.c
134
src/botl.c
@@ -1,4 +1,4 @@
|
||||
/* NetHack 3.6 botl.c $NHDT-Date: 1557094795 2019/05/05 22:19:55 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.145 $ */
|
||||
/* NetHack 3.6 botl.c $NHDT-Date: 1562114350 2019/07/03 00:39:10 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.146 $ */
|
||||
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
||||
/*-Copyright (c) Michael Allison, 2006. */
|
||||
/* NetHack may be freely redistributed. See license for details. */
|
||||
@@ -433,6 +433,8 @@ struct istat_s {
|
||||
const char *fldfmt;
|
||||
long time; /* moves when this field hilite times out */
|
||||
boolean chg; /* need to recalc time? */
|
||||
boolean percent_matters;
|
||||
short percent_value;
|
||||
unsigned anytype;
|
||||
anything a;
|
||||
char *val;
|
||||
@@ -452,6 +454,7 @@ STATIC_DCL void NDECL(init_blstats);
|
||||
STATIC_DCL int FDECL(compare_blstats, (struct istat_s *, struct istat_s *));
|
||||
STATIC_DCL char *FDECL(anything_to_s, (char *, anything *, int));
|
||||
STATIC_DCL int FDECL(percentage, (struct istat_s *, struct istat_s *));
|
||||
STATIC_DCL int NDECL(exp_percentage);
|
||||
|
||||
#ifdef STATUS_HILITES
|
||||
STATIC_DCL void FDECL(s_to_anything, (anything *, char *, int));
|
||||
@@ -498,14 +501,18 @@ STATIC_DCL boolean FDECL(status_hilite_menu_add, (int));
|
||||
#define INIT_THRESH /*empty*/
|
||||
#endif
|
||||
|
||||
#define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
|
||||
{ name, fmtstr, 0L, FALSE, anytyp, { (genericptr_t) 0 }, (char *) 0, \
|
||||
#define INIT_BLSTAT(name, fmtstr, anytyp, wid, fld) \
|
||||
{ name, fmtstr, 0L, FALSE, FALSE, 0, anytyp, \
|
||||
{ (genericptr_t) 0 }, (char *) 0, \
|
||||
wid, -1, fld INIT_THRESH }
|
||||
#define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
|
||||
{ name, fmtstr, 0L, FALSE, anytyp, { (genericptr_t) 0 }, (char *) 0, \
|
||||
#define INIT_BLSTATP(name, fmtstr, anytyp, wid, maxfld, fld) \
|
||||
{ name, fmtstr, 0L, FALSE, TRUE, 0, anytyp, \
|
||||
{ (genericptr_t) 0 }, (char *) 0, \
|
||||
wid, maxfld, fld INIT_THRESH }
|
||||
|
||||
/* If entries are added to this, botl.h will require updating too */
|
||||
/* If entries are added to this, botl.h will require updating too.
|
||||
'max' value of BL_EXP gets special handling since the percentage
|
||||
involved isn't a direct 100*current/maximum calculation. */
|
||||
STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
|
||||
INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE),
|
||||
INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR),
|
||||
@@ -520,7 +527,7 @@ STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
|
||||
INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD),
|
||||
INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE),
|
||||
INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX),
|
||||
INIT_BLSTAT("experience-level", " Xp:%s", ANY_INT, 10, BL_XP),
|
||||
INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT, 10, BL_EXP, BL_XP),
|
||||
INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC),
|
||||
INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD),
|
||||
INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME),
|
||||
@@ -529,7 +536,7 @@ STATIC_VAR struct istat_s initblstats[MAXBLSTATS] = {
|
||||
INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP),
|
||||
INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX),
|
||||
INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC),
|
||||
INIT_BLSTAT("experience", "/%s", ANY_LONG, 20, BL_EXP),
|
||||
INIT_BLSTATP("experience", "/%s", ANY_LONG, 20, BL_EXP, BL_EXP),
|
||||
INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION)
|
||||
};
|
||||
|
||||
@@ -763,8 +770,8 @@ boolean *valsetlist;
|
||||
int pc, chg, color = NO_COLOR;
|
||||
unsigned anytype;
|
||||
boolean updated = FALSE, reset;
|
||||
struct istat_s *curr = NULL, *prev = NULL;
|
||||
enum statusfields idxmax;
|
||||
struct istat_s *curr, *prev;
|
||||
enum statusfields fldmax;
|
||||
|
||||
/*
|
||||
* Now pass the changed values to window port.
|
||||
@@ -775,6 +782,31 @@ boolean *valsetlist;
|
||||
color = NO_COLOR;
|
||||
|
||||
chg = update_all ? 0 : compare_blstats(prev, curr);
|
||||
/*
|
||||
* TODO:
|
||||
* Dynamically update 'percent_matters' as rules are added or
|
||||
* removed to track whether any of them are precentage rules.
|
||||
* Then there'll be no need to assume that non-Null 'thresholds'
|
||||
* means that percentages need to be kept up to date.
|
||||
* [Affects exp_percent_changing() too.]
|
||||
*/
|
||||
if (((chg || update_all || fld == BL_XP)
|
||||
&& curr->percent_matters && curr->thresholds)
|
||||
/* when 'hitpointbar' is On, percent matters even if HP
|
||||
hasn't changed and has no percentage rules (in case HPmax
|
||||
has changed when HP hasn't, where we ordinarily wouldn't
|
||||
update HP so would miss an update of the hitpoint bar) */
|
||||
|| (fld == BL_HP && iflags.wc2_hitpointbar)) {
|
||||
fldmax = curr->idxmax;
|
||||
pc = (fldmax == BL_EXP) ? exp_percentage()
|
||||
: (fldmax >= 0) ? percentage(curr, &blstats[idx][fldmax])
|
||||
: 0; /* bullet proofing; can't get here */
|
||||
if (pc != prev->percent_value)
|
||||
chg = 1;
|
||||
curr->percent_value = pc;
|
||||
} else {
|
||||
pc = 0;
|
||||
}
|
||||
|
||||
/* Temporary? hack: moveloop()'s prolog for a new game sets
|
||||
* context.rndencode after the status window has been init'd,
|
||||
@@ -811,19 +843,7 @@ boolean *valsetlist;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* TODO?
|
||||
* It's possible for HPmax (or ENEmax) to change while current
|
||||
* HP (or energy) stays the same. [Perhaps current and maximum
|
||||
* both go up, then before the next status update takes place
|
||||
* current goes down again.] If that happens with HPmax, we
|
||||
* ought to force the windowport to treat current HP as changed
|
||||
* if hitpointbar is On, in order for that to be re-rendered.
|
||||
*/
|
||||
if (update_all || chg || reset) {
|
||||
idxmax = curr->idxmax;
|
||||
pc = (idxmax >= 0) ? percentage(curr, &blstats[idx][idxmax]) : 0;
|
||||
|
||||
if (!valsetlist[fld])
|
||||
(void) anything_to_s(curr->val, &curr->a, anytype);
|
||||
|
||||
@@ -943,6 +963,7 @@ boolean reassessment; /* TRUE: just recheck fields w/o other initialization */
|
||||
status_enablefield(fld, fieldname, fieldfmt, fldenabl);
|
||||
}
|
||||
update_all = TRUE;
|
||||
context.botlx = TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1260,6 +1281,75 @@ struct istat_s *bl, *maxbl;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* percentage for both xp (level) and exp (points) is the percentage for
|
||||
(curr_exp - this_level_start) in (next_level_start - this_level_start) */
|
||||
STATIC_OVL int
|
||||
exp_percentage()
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (u.ulevel < 30) {
|
||||
long exp_val, nxt_exp_val, curlvlstart;
|
||||
|
||||
curlvlstart = newuexp(u.ulevel - 1);
|
||||
exp_val = u.uexp - curlvlstart;
|
||||
nxt_exp_val = newuexp(u.ulevel) - curlvlstart;
|
||||
if (exp_val == nxt_exp_val - 1L) {
|
||||
/*
|
||||
* Full 100% is unattainable since hero gains a level
|
||||
* and the threshold for next level increases, but treat
|
||||
* (next_level_start - 1 point) as a special case. It's a
|
||||
* key value after being level drained so is something that
|
||||
* some players would like to be able to highlight distinctly.
|
||||
*/
|
||||
res = 100;
|
||||
} else {
|
||||
struct istat_s curval, maxval;
|
||||
|
||||
curval.anytype = maxval.anytype = ANY_LONG;
|
||||
curval.a = maxval.a = zeroany;
|
||||
curval.a.a_long = exp_val;
|
||||
maxval.a.a_long = nxt_exp_val;
|
||||
/* maximum delta between levels is 10000000; calculation of
|
||||
100 * (10000000 - N) / 10000000 fits within 32-bit long */
|
||||
res = percentage(&curval, &maxval);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/* experience points have changed but experience level hasn't; decide whether
|
||||
botl update is needed for a different percentage highlight rule for Xp */
|
||||
boolean
|
||||
exp_percent_changing()
|
||||
{
|
||||
int pc, color_dummy;
|
||||
anything a;
|
||||
struct hilite_s *rule;
|
||||
struct istat_s *curr;
|
||||
|
||||
/* if status update is already requested, skip this processing */
|
||||
if (!context.botl) {
|
||||
/*
|
||||
* Status update is warranted iff percent integer changes and the new
|
||||
* percentage results in a different highlighting rule being selected.
|
||||
*/
|
||||
curr = &blstats[now_or_before_idx][BL_XP];
|
||||
/* TODO: [see eval_notify_windowport_field() about percent_matters
|
||||
and the check against 'thresholds'] */
|
||||
if (curr->percent_matters && curr->thresholds
|
||||
&& (pc = exp_percentage()) != curr->percent_value) {
|
||||
a = zeroany;
|
||||
a.a_int = (int) u.ulevel;
|
||||
rule = get_hilite(now_or_before_idx, BL_XP,
|
||||
(genericptr_t) &a, 0, pc, &color_dummy);
|
||||
if (rule != curr->hilite_rule)
|
||||
return TRUE; /* caller should set 'context.botl' to True */
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* callback so that interface can get capacity index rather than trying
|
||||
to reconstruct that from the encumbrance string or asking the general
|
||||
core what the value is */
|
||||
|
||||
11
src/exper.c
11
src/exper.c
@@ -1,4 +1,4 @@
|
||||
/* NetHack 3.6 exper.c $NHDT-Date: 1553296396 2019/03/22 23:13:16 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.32 $ */
|
||||
/* NetHack 3.6 exper.c $NHDT-Date: 1562114352 2019/07/03 00:39:12 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.33 $ */
|
||||
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
||||
/*-Copyright (c) Robert Patrick Rankin, 2007. */
|
||||
/* NetHack may be freely redistributed. See license for details. */
|
||||
@@ -14,6 +14,8 @@ long
|
||||
newuexp(lev)
|
||||
int lev;
|
||||
{
|
||||
if (lev < 1) /* for newuexp(u.ulevel - 1) when u.ulevel is 1 */
|
||||
return 0L;
|
||||
if (lev < 10)
|
||||
return (10L * (1L << lev));
|
||||
if (lev < 20)
|
||||
@@ -177,6 +179,11 @@ register int exper, rexp;
|
||||
u.uexp = newexp;
|
||||
if (flags.showexp)
|
||||
context.botl = TRUE;
|
||||
/* even when experience points aren't being shown, experience level
|
||||
might be highlighted with a percentage highlight rule and that
|
||||
percentage depends upon experience points */
|
||||
if (!context.botl && exp_percent_changing())
|
||||
context.botl = TRUE;
|
||||
}
|
||||
/* newrexp will always differ from oldrexp unless they're LONG_MAX */
|
||||
if (newrexp != oldrexp) {
|
||||
@@ -303,7 +310,7 @@ boolean incr; /* true iff via incremental experience growth */
|
||||
}
|
||||
++u.ulevel;
|
||||
pline("Welcome %sto experience level %d.",
|
||||
u.ulevelmax < u.ulevel ? "" : "back ",
|
||||
(u.ulevelmax < u.ulevel) ? "" : "back ",
|
||||
u.ulevel);
|
||||
if (u.ulevelmax < u.ulevel)
|
||||
u.ulevelmax = u.ulevel;
|
||||
|
||||
Reference in New Issue
Block a user