From 1e7fb839a3a55302457c1e67e400848aa0a0fd9f Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 2 Jul 2019 17:39:23 -0700 Subject: [PATCH] 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). --- doc/Guidebook.mn | 21 +++++++- doc/Guidebook.tex | 19 ++++++- doc/fixes36.3 | 6 ++- include/extern.h | 3 +- src/botl.c | 134 ++++++++++++++++++++++++++++++++++++++-------- src/exper.c | 11 +++- 6 files changed, 165 insertions(+), 29 deletions(-) diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index a2d922a12..7a4ffd8d6 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -1,4 +1,4 @@ -.\" $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.305 $ $NHDT-Date: 1557251604 2019/05/07 17:53:24 $ +.\" $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.310 $ $NHDT-Date: 1562114349 2019/07/03 00:39:09 $ .\" .\" This is an excerpt from the 'roff' man page from the 'groff' package. .\" NetHack's Guidebook.mn currently does *not* adhere to these guidelines. @@ -4284,7 +4284,24 @@ it also matches when value is below or above the percentage. Use prefix \(oq<\(cq or \(oq>\(cq to match when strictly below or above. (The numeric limit is relaxed slightly for those: \f(CR>-1%\fP and \f(CR<101%\fP are allowed.) -Only valid for \(lqhitpoints\(rq and \(lqpower\(rq fields. +Only four fields support percentage rules. +Percentages for \(lqhitpoints\(rq and \(lqpower\(rq are +straightforward; they're based on the corresponding maximum field. +Percentage highlight rules are also allowed for \(lqexperience level\(rq +and \(lqexperience points\(rq (valid when the +.op showexp +option is enabled). +For those, the percentage is based on the progress from the start of +the current experience level to the start of the next level. +So if level 2 starts at 20 points and level 3 starts at 40 points, +having 30 points is 50% and 35 points is 75%. +100% is unattainable for experience because you'll gain a level and +the calculations will be reset for that new level, but a rule for +\f(CR=100%\fP is allowed and matches the special case of being +exactly 1 experience point short of the next level. +.\" (If you manage to reach level 30, there is no next level and the +.\" percentage will remain at 0% no matter have many additional experience +.\" points you earn.) .lp "*" absolute value sets the attribute when the field value matches that number. diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 1b32dabc7..bc9b5e00f 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -4773,7 +4773,24 @@ it also matches when value is below or above the percentage. Use prefix `{\tt <}' or `{\tt >}' to match when strictly below or above. (The numeric limit is relaxed slightly for those: {\tt >-1\%} and {\tt <101\%} are allowed.) -Only valid for ``{\it hitpoints\/}'' and ``{\it power\/}'' fields. +Only four fields support percentage rules. +Percentages for ``{\it hitpoints\/}'' and ``{\it power\/}'' are +straightforward; they're based on the corresponding maximum field. +Percentage highlight rules are also allowed for ``{\it experience level\/}'' +and ``{\it experience points\/}'' (valid when the +(\it showexp\/} +option is enabled). +For those, the percentage is based on the progress from the start of +the current experience level to the start of the next level. +So if level 2 starts at 20 points and level 3 starts at 40 points, +having 30 points is 50\% and 35 points is 75\%. +100\% is unattainable for experience because you'll gain a level and +the calculations will be reset for that new level, but a rule for +{\tt =100\%} is allowed and matches the special case of being +exactly 1 experience point short of the next level. +% (If you manage to reach level 30, there is no next level and the +% percentage will remain at 0\% no matter have many additional experience +% points you earn.) %.lp "*" \item{\bb{}} absolute value sets the attribute when the field value diff --git a/doc/fixes36.3 b/doc/fixes36.3 index 2cec56c67..22532246b 100644 --- a/doc/fixes36.3 +++ b/doc/fixes36.3 @@ -1,4 +1,4 @@ -$NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.81 $ $NHDT-Date: 1562056615 2019/07/02 08:36:55 $ +$NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.83 $ $NHDT-Date: 1562114348 2019/07/03 00:39:08 $ This fixes36.3 file is here to capture information about updates in the 3.6.x lineage following the release of 3.6.2 in May 2019. Please note, however, @@ -206,6 +206,10 @@ if you reach the edge of a level (relatively uncommon) and try to move off, 'attributes' disclosure at end of game includes number of experience points that were needed to reach the next experience level (new for normal play and explore mode; previously only shown for wizard mode) +status highlighting using percentage rules now supported for experience level + and experience points; for both, percent is based on Exp progress from + the start of the current Xp level to the start of the next Xp level; + 100% isn't possible so used as special case for next_Xp_lvl - 1 Exp_pt wizard-mode: display effect to show where an unseen wished-for monster landed curses: enable latent mouse support curses: give menus and text windows a minimum size of 5x25 since tiny ones can diff --git a/include/extern.h b/include/extern.h index a64b98783..90548cf37 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1,4 +1,4 @@ -/* NetHack 3.6 extern.h $NHDT-Date: 1560161804 2019/06/10 10:16:44 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.714 $ */ +/* NetHack 3.6 extern.h $NHDT-Date: 1562114349 2019/07/03 00:39:09 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.715 $ */ /* Copyright (c) Steve Creps, 1988. */ /* NetHack may be freely redistributed. See license for details. */ @@ -177,6 +177,7 @@ E long NDECL(botl_score); E int FDECL(describe_level, (char *)); E void FDECL(status_initialize, (BOOLEAN_P)); E void NDECL(status_finish); +E boolean NDECL(exp_percent_changing); E int NDECL(stat_cap_indx); E int NDECL(stat_hunger_indx); E const char *FDECL(bl_idx_to_fldname, (int)); diff --git a/src/botl.c b/src/botl.c index f43c0f498..06f0a01b5 100644 --- a/src/botl.c +++ b/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 */ diff --git a/src/exper.c b/src/exper.c index 0cead3e97..9b96b5c43 100644 --- a/src/exper.c +++ b/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; -- 2.50.0