]> granicus.if.org Git - nethack/commitdiff
status_hilite for Xp and Exp by percent rules
authorPatR <rankin@nethack.org>
Wed, 3 Jul 2019 00:39:23 +0000 (17:39 -0700)
committerPatR <rankin@nethack.org>
Wed, 3 Jul 2019 00:39:23 +0000 (17:39 -0700)
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
doc/Guidebook.tex
doc/fixes36.3
include/extern.h
src/botl.c
src/exper.c

index a2d922a1292a99af0ca6ca6721c78a166dea8d1e..7a4ffd8d6bde6d17b7cdb2553988d2c4a4679c87 100644 (file)
@@ -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.
index 1b32dabc73efb06c29cc2f387b38f934a2796fa0..bc9b5e00f9a38bb47aba09939f9d31ffb24d4c49 100644 (file)
@@ -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
index 2cec56c678a9d13054a7c4eb3b67a4c34cd7d487..22532246bd08a650524c2020bc7dfb8e0daea05c 100644 (file)
@@ -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
index a64b987835daa49a333a0bb0388822fa3c0ba375..90548cf37bf58f2dc62c5c269a6d685b79db25a0 100644 (file)
@@ -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));
index f43c0f49872719c62ded8825995d04681b934a7e..06f0a01b5a1dccc4172744ee51f3772fad509f3a 100644 (file)
@@ -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 */
index 0cead3e978e91a755774a8b97d66ac7e729d2b9f..9b96b5c438f1ccd9b5256bfa26f4be3ba916df75 100644 (file)
@@ -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;