]> granicus.if.org Git - nethack/commitdiff
add some new, easier achievements
authorPatR <rankin@nethack.org>
Wed, 12 Feb 2020 22:35:37 +0000 (14:35 -0800)
committerPatR <rankin@nethack.org>
Wed, 12 Feb 2020 22:35:37 +0000 (14:35 -0800)
Introduce eight achievements that can be attained by more players.
 Entered Gnomish Mines  - self explanatory
 Entered Mine Town      - the town portion, not just the level
 Entered a shop         - any tended shop on any level
 Entered a temple       - likewise for temple
 Consulted the Oracle   - bought at least one major or minor oracle
 Read a Discworld Novel - read at least one passage
 Entered Sokoban        - like mines
 Entered the Big Room   - not always possible since not always present

The novel and bigroom ones aren't always achieveable since novels are
only guaranteed if a book or scroll shop gets created and bigroom is
only guaranteed in wizard mode.  No one ever claimed that every
possible achievement can be attained in a single game.  (If one for
entering the Fort Ludios level--or perhaps entering the Fort itself--
eventually gets add, that won't be possible in every game either.)

The mine town one probably needs some tweaking.  Two of the town's
seven variants have no town boundary (despite a rectangular area of
pre-defined map) and at present simply arriving on either of those
levels is enough to be credited with the entered-town achievement.

Bump EDITLEVEL because u.uachieved[] has increased in size.  This
time it has been expanded to the maximum that xlogfile's bitmask of
achievements can handle, enough for up to 9 more achievements without
another EDITLEVEL increment.

15 files changed:
doc/Guidebook.mn
doc/Guidebook.tex
include/context.h
include/patchlevel.h
include/you.h
src/cmd.c
src/do.c
src/hack.c
src/insight.c
src/invent.c
src/priest.c
src/rumors.c
src/shk.c
src/spell.c
src/topten.c

index faf3a992c5d949ef883928fcc1f400e353a35525..f79c5ad3c7e4fe459b5a1b4fb66dae94839d02a7 100644 (file)
@@ -2615,8 +2615,9 @@ Statues and boulders are not particularly useful, and are generally
 heavy.  It is rumored that some statues are not what they seem.
 .pg
 Boulders occasionally block your path.
-You can push one forward when nothing blocks \fIits\fP path, or you can
-smash it into a pile of small rocks with breaking magic or a pick-exe.
+You can push one forward (by attempting to walk onto its spot)
+when nothing blocks \fIits\fP path, or you can
+smash it into a pile of small rocks with breaking magic or a pick-axe.
 Very large humanoids (giants and their ilk) have been known to pick up
 boulders and use them as missile weapons.
 .pg
@@ -2820,9 +2821,24 @@ in which you might accomplish them.
 .PS "Mines'\~End\~"
 .\}
 .fi
-.\" Use separate if-else because other achievements will be inserted here.
-.ie \n(fF \{\
+.PL Shop
+Entered a shop.
+.PL Temple
+Entered a temple.
+.PL Mines
+Entered the Gnomish Mines.
+.PL Town
+Entered Mine Town.
+.PL Oracle
+Consulted the Oracle of Delphi.
+.PL Novel
+Read a passage from a Discworld Novel.
 .PL Sokoban
+Entered Sokoban.
+.PL "Big\~Room"
+Entered the Big Room.
+.ie \n(fF \{\
+.PL "Soko-Prize"
 Explored to the top of Sokoban
 .br
 and found a special item there.
@@ -2832,7 +2848,7 @@ Explored to the bottom of the Gnomish Mines
 and found a special item there.
 .\}
 .el \{\
-.PL Sokoban
+.PL "Soko-Prize"
 Explored to the top of Sokoban and found a special item there.
 .PL "Mines'\~End"
 Explored to the bottom of the Gnomish Mines and found a special item there.
@@ -2866,6 +2882,11 @@ Delivered the Amulet to its final destination.
 .ED
 .lp "Notes:  "
 .pg
+There's no guaranteed \fINovel\fP so the achievement to read one might
+not always be attainable (except perhaps by \fIwishing\fP).
+Similarly, the \fIBig Room\fP level is not always present.
+Unlike with the Novel, there's no way to wish for this opportunity.
+.pg
 The \(lqspecial items\(rq hidden in \fIMines'\~End\fP and \fISokoban\fP
 are not unique but are considered to be prizes or rewards
 for exploring those levels since doing so is not necessary to complete
index 0340cc03607980edbe883754d4ebbaf080c0b609..a5f44543f9b09bf510a03a1efcf8ba14a03acfaa 100644 (file)
@@ -2855,8 +2855,9 @@ heavy.  It is rumored that some statues are not what they seem.
 
 %.pg
 Boulders occasionally block your path.
-You can push one forward when nothing blocks {\it its\/} path, or you can
-smash it into a pile of small rocks with breaking magic or a pick-exe.
+You can push one forward (by attempting to walk onto its spot)
+when nothing blocks {\it its\/} path, or you can
+smash it into a pile of small rocks with breaking magic or a pick-axe.
 Very large humanoids (giants and their ilk) have been known to pick up
 boulders and use them as missile weapons.
 
@@ -3058,8 +3059,24 @@ in which you might accomplish them.
 \settowidth{\achwidth}{\tt Mines'~End~}
 \addtolength{\achwidth}{\labelsep}
 \blist{\leftmargin \achwidth \topsep 1mm \itemsep 0mm}
-%.PL Sokoban
+%.PL Shop
+\item[{\tt Shop}]
+Entered a shop.
+\item[{\tt Temple}]
+Entered a temple.
+\item[{\tt Mines}]
+Entered the Gnomish Mines.
+\item[{\tt Town}]
+Entered Mine Town.
+\item[{\tt Oracle}]
+Consulted the Oracle of Delphi.
+\item[{\tt Novel}]
+Read a passage from a Discworld Novel.
 \item[{\tt Sokoban}]
+Entered Sokoban.
+\item[{\tt "Big~Room"}]
+Entered the Big Room.
+\item[{\tt "Soko-Prize"}]
 Explored to the top of Sokoban and found a special item there.
 \item[{\tt Mines'~End}]
 Explored to the bottom of the Gnomish Mines and found a special item there.
index a872fa40550cf5dd3bec5ab0549f394936ff6e38..e2bd9b49f64bf92f068eff79ee77635cd8df6231 100644 (file)
@@ -106,8 +106,10 @@ struct novel_tracking { /* for choosing random passage when reading novel */
 };
 
 struct achievement_tracking {
-    unsigned mines_prize_oid, soko_prize_oid; /* obj->o_id */
-    /* short mines_prize_type, soko_prize_typ1, soko_prize_typ2; */
+    unsigned mines_prize_oid,  /* luckstone->o_id */
+             soko_prize_oid,   /* {bag or amulet}->o_id */
+             castle_prize_old; /* wand->o_id; not yet implemented */
+    boolean minetn_reached;    /* avoid redundant checking for town entry */
 };
 
 struct context_info {
index 3869fbe321199e93839c2fd52ad73429a8350b67..7be0bce3ff36431795f0748b3a057cba14afae94 100644 (file)
@@ -14,7 +14,7 @@
  * Incrementing EDITLEVEL can be used to force invalidation of old bones
  * and save files.
  */
-#define EDITLEVEL 15
+#define EDITLEVEL 16
 
 #define COPYRIGHT_BANNER_A "NetHack, Copyright 1985-2020"
 #define COPYRIGHT_BANNER_B \
index f017f0e0ccaf1bba6b80f717696774922324bc3b..b51c34c6751a24403d38464208b713dc44d2fdc5 100644 (file)
@@ -54,26 +54,76 @@ struct u_event {
     Bitfield(ascended, 1);          /* has offered the Amulet */
 };
 
-/* numerical order of these matters because they've been encoded in a
-   bitmask in xlogfile; reordering would break decoding that; during play
-   the number doesn't matter--they're recorded in the order achieved */
+/*
+ * Achievements:  milestones reached during the current game.
+ * Numerical order of these matters because they've been encoded in
+ * a bitmask in xlogfile.  Reordering would break decoding that.
+ * Aside from that, the number isn't significant--they're recorded
+ * and eventually disclosed in the order achieved.
+ *
+ * Since xlogfile could be post-processed by unknown tools, we should
+ * limit these to 31 total (it's possible that 32-bit signed longs are
+ * the best such tools can offer).  Eventually that is likely to need
+ * to change, probably by giving xlogfile an achieve2 field rather
+ * than by assuming that 64-bit longs are viable or by squeezing in a
+ * 32nd entry by switching to unsigned long.
+ */
 enum achivements {
     ACH_BELL =  1, /* acquired Bell of Opening */
     ACH_HELL =  2, /* entered Gehennom */
     ACH_CNDL =  3, /* acquired Candelabrum of Invocation */
     ACH_BOOK =  4, /* acquired Book of the Dead */
     ACH_INVK =  5, /* performed invocation to gain access to Sanctum */
-    ACH_AMUL =  6, /* acuired The Amulet */
+    ACH_AMUL =  6, /* acquired The Amulet */
     ACH_ENDG =  7, /* entered end game */
     ACH_ASTR =  8, /* entered Astral Plane */
     ACH_UWIN =  9, /* ascended */
-    ACH_LUCK = 10, /* acquired Mines' End luckstone */
-    ACH_SOKO = 11, /* acquired Sokoban bag of holding or amu of reflection */
+    ACH_MINE_PRIZE = 10, /* acquired Mines' End luckstone */
+    ACH_SOKO_PRIZE = 11, /* acquired Sokoban bag or amulet */
     ACH_MEDU = 12, /* killed Medusa */
     ACH_BLND = 13, /* hero was always blond, no, blind */
     ACH_NUDE = 14, /* hero never wore armor */
-    N_ACH
+    /* 1 through 14 were present in 3.6.x; the rest are newer; first,
+       some easier ones so less skilled players can have achievements */
+    ACH_MINE = 15, /* entered Gnomish Mines */
+    ACH_TOWN = 16, /* reached Mine Town */
+    ACH_SHOP = 17, /* entered a shop */
+    ACH_TMPL = 18, /* entered a temple */
+    ACH_ORCL = 19, /* consulted the Oracle */
+    ACH_NOVL = 20, /* read at least one passage from a Discworld novel */
+    ACH_SOKO = 21, /* entered Sokoban */
+    ACH_BGRM = 22, /* entered Bigroom (not guaranteed to be in every dgn) */
+    /* 23..31, 9 available potential achievements; #32 currently off-limits */
+    N_ACH = 32     /* allocate room for 31 plus a slot for 0 terminator */
 };
+    /*
+     * Other potential achievements to track (this comment briefly resided
+     * in encodeachieve(topten.c) and has been revised since moving here:
+     *  [reached experience level N for a few interesting values of N
+     *   or perhaps "became a <rank title>" for each new rank reached]
+     *  got quest summons,
+     *  entered quest branch,
+     *  chatted with leader,
+     *  entered second or lower quest level (implies leader gave the Ok),
+     *  entered last quest level,
+     *  defeated nemesis (not same as acquiring Bell or artifact),
+     *  completed quest (formally, by bringing artifact to leader),
+     *  entered rogue level,
+     *  entered Fort Ludios level/branch (not guaranteed to be achieveable),
+     *  entered Medusa level,
+     *  entered castle level,
+     *  opened castle drawbridge,
+     *  obtained castle wand (handle similarly to mines and sokoban prizes),
+     *  passed Valley level (entered-Gehennom already covers Valley itself),
+     *  [assorted demon lairs?],
+     *  entered Vlad's tower branch,
+     *  defeated Vlad (not same as acquiring Candelabrum),
+     *  entered Wizard's tower area within relevant level,
+     *  defeated Wizard,
+     *  found vibrating square,
+     *  entered sanctum level (maybe not; too close to performed-invocation),
+     *  [defeated Famine, defeated Pestilence, defeated Death]
+     */
 
 struct u_realtime {
     long   realtime;     /* accumulated playing time in seconds */
index 94d5e245b6fefe5991b8f5200c17c335e18fd4ab..379054f44200f2bd207f0b3f1f63a58f1da6f078 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -792,12 +792,12 @@ boolean pre, wiztower;
                 static const char Unachieve[] = "%s achievement revoked.";
 
                 if (Is_mineend_level(&u.uz)) {
-                    if (remove_achievement(ACH_LUCK))
-                        pline(Unachieve, "Mine's end");
+                    if (remove_achievement(ACH_MINE_PRIZE))
+                        pline(Unachieve, "Mine's-end");
                     g.context.achieveo.mines_prize_oid = 0;
                 } else if (Is_sokoend_level(&u.uz)) {
-                    if (remove_achievement(ACH_SOKO))
-                        pline(Unachieve, "Sokoban end");
+                    if (remove_achievement(ACH_SOKO_PRIZE))
+                        pline(Unachieve, "Sokoban-end");
                     g.context.achieveo.soko_prize_oid = 0;
                 }
             }
index 70d51f8dacbd609e4f1b2189f67612f9c711cd27..f7d60eec9b9397b8d1d7c0969567ed6d8da2feb1 100644 (file)
--- a/src/do.c
+++ b/src/do.c
@@ -1624,12 +1624,6 @@ boolean at_stairs, falling, portal;
     /* special levels can have a custom arrival message */
     deliver_splev_message();
 
-    /* give room entrance message, if any */
-    check_special_room(FALSE);
-
-    /* deliver objects traveling with player */
-    obj_delivery(TRUE);
-
     /* Check whether we just entered Gehennom. */
     if (!In_hell(&u.uz0) && Inhell) {
         if (Is_valley(&u.uz)) {
@@ -1701,9 +1695,18 @@ boolean at_stairs, falling, portal;
                 mtmp->msleeping = 0;
             }
         }
+    } else if (In_mines(&u.uz)) {
+        if (newdungeon)
+            record_achievement(ACH_MINE);
+    } else if (In_sokoban(&u.uz)) {
+        if (newdungeon)
+            record_achievement(ACH_SOKO);
     } else {
-        if (new && Is_rogue_level(&u.uz))
+        if (new && Is_rogue_level(&u.uz)) {
             You("enter what seems to be an older, more primitive world.");
+        } else if (new && Is_bigroom(&u.uz)) {
+            record_achievement(ACH_BGRM);
+        }
         /* main dungeon message from your quest leader */
         if (!In_quest(&u.uz0) && at_dgn_entrance("The Quest")
             && !(u.uevent.qcompleted || u.uevent.qexpelled
@@ -1727,6 +1730,11 @@ boolean at_stairs, falling, portal;
     if ((annotation = get_annotation(&u.uz)) != 0)
         You("remember this level as %s.", annotation);
 
+    /* give room entrance message, if any */
+    check_special_room(FALSE);
+    /* deliver objects traveling with player */
+    obj_delivery(TRUE);
+
     /* assume this will always return TRUE when changing level */
     (void) in_out_region(u.ux, u.uy);
     (void) pickup(1);
index 84639f63ebc22b733d02a828d6490c5727eb9bf7..18c0157eab3f4ee8475bfcfb013b3c46c3c79abf 100644 (file)
@@ -2198,7 +2198,7 @@ boolean pick;
     check_special_room(FALSE);
     if (IS_SINK(levl[u.ux][u.uy].typ) && Levitation)
         dosinkfall();
-    if (!g.in_steed_dismounting) { /* if dismounting, we'll check again later */
+    if (!g.in_steed_dismounting) { /* if dismounting, check again later */
         boolean pit;
 
         /* if levitation is due to time out at the end of this
@@ -2462,7 +2462,7 @@ register boolean newlev;
 /* possibly deliver a one-time room entry message */
 void
 check_special_room(newlev)
-register boolean newlev;
+boolean newlev;
 {
     register struct monst *mtmp;
     char *ptr;
@@ -2475,6 +2475,12 @@ register boolean newlev;
     if (!*u.uentered && !*u.ushops_entered) /* implied by newlev */
         return; /* no entrance messages necessary */
 
+    if (!g.context.achieveo.minetn_reached
+        && In_mines(&u.uz) && in_town(u.ux, u.uy)) {
+        record_achievement(ACH_TOWN);
+        g.context.achieveo.minetn_reached = TRUE;
+    }
+
     /* Did we just enter a shop? */
     if (*u.ushops_entered)
         u_entered_shop(u.ushops_entered);
@@ -2504,6 +2510,7 @@ register boolean newlev;
         case MORGUE:
             if (midnight()) {
                 const char *run = locomotion(g.youmonst.data, "Run");
+
                 pline("%s away!  %s away!", run, run);
             } else
                 You("have an uncanny feeling...");
@@ -2528,6 +2535,7 @@ register boolean newlev;
             break;
         case DELPHI: {
             struct monst *oracle = monstinroom(&mons[PM_ORACLE], roomno);
+
             if (oracle) {
                 if (!oracle->mpeaceful)
                     verbalize("You're in Delphi, %s.", g.plname);
@@ -2586,7 +2594,6 @@ register boolean newlev;
                 }
         }
     }
-
     return;
 }
 
index 8ecc3b43404171bad5abde7cd5ca94ae22fc812f..c368c44031e98149b29545024b638c5197e61302 100644 (file)
@@ -1755,14 +1755,38 @@ int final; /* used "behind the curtain" by enl_foo() macros */
         case ACH_NUDE:
             enl_msg(You_, "have gone", "went", " without any armor", "");
             break;
-        case ACH_LUCK:
-            enl_msg(You_, "have ", "", "completed the Gnomish Mines", "");
+        case ACH_MINE:
+            you_have_X("entered the Gnomish Mines");
+            break;
+        case ACH_TOWN:
+            you_have_X("entered Mine Town");
+            break;
+        case ACH_SHOP:
+            you_have_X("entered a shop");
+            break;
+        case ACH_TMPL:
+            you_have_X("entered a temple");
+            break;
+        case ACH_ORCL:
+            you_have_X("consulted the Oracle of Delphi");
+            break;
+        case ACH_NOVL:
+            you_have_X("read from a Discworld novel");
             break;
         case ACH_SOKO:
-            enl_msg(You_, "have ", "", "completed Sokoban", "");
+            you_have_X("entered Sokoban");
+            break;
+        case ACH_SOKO_PRIZE: /* hard to reach guaranteed bag or amulet */
+            you_have_X("completed Sokoban");
+            break;
+        case ACH_MINE_PRIZE: /* hidden guaranteed luckstone */
+            you_have_X("completed the Gnomish Mines");
+            break;
+        case ACH_BGRM:
+            you_have_X("entered the Big Room");
             break;
         case ACH_MEDU:
-            enl_msg(You_, "have ", "", "defeated Medusa", "");
+            you_have_X("defeated Medusa");
             break;
         case ACH_BELL:
             /* alternate phrasing for present vs past and also for
@@ -1788,8 +1812,7 @@ int final; /* used "behind the curtain" by enl_foo() macros */
                     " the Book of the Dead", "");
             break;
         case ACH_INVK:
-            enl_msg(You_, "have ", "",
-                    "gained access to Moloch's Sanctum", "");
+            you_have_X("gained access to Moloch's Sanctum");
             break;
         case ACH_AMUL:
             /* alternate wording for ascended (always past tense) since
@@ -1805,10 +1828,10 @@ int final; /* used "behind the curtain" by enl_foo() macros */
            be redundant and ascending makes both be redundant, but
            we display all that apply */
         case ACH_ENDG:
-            enl_msg(You_, "have ", "", "reached the Elemental Planes", "");
+            you_have_X("reached the Elemental Planes");
             break;
         case ACH_ASTR:
-            enl_msg(You_, "have ", "", "reached the Astral Plane", "");
+            you_have_X("reached the Astral Plane");
             break;
         case ACH_UWIN:
             /* the ultimate achievement... */
index 51568945e35095196d911735a4341fc020ebd378..3c0347ad9be9fbe61be7992436b7b875404856a1 100644 (file)
@@ -839,11 +839,11 @@ struct obj *obj;
     /* "special achievements"; revealed in end of game disclosure and
        dumplog, originally just recorded in XLOGFILE */
     if (is_mines_prize(obj)) {
-        record_achievement(ACH_LUCK);
+        record_achievement(ACH_MINE_PRIZE);
         g.context.achieveo.mines_prize_oid = 0; /* done with luckstone o_id */
         obj->nomerge = 0;
     } else if (is_soko_prize(obj)) {
-        record_achievement(ACH_SOKO);
+        record_achievement(ACH_SOKO_PRIZE);
         g.context.achieveo.soko_prize_oid = 0; /* done with bag/amulet o_id */
         obj->nomerge = 0;
     }
index 259597e7d447b6a6764bd917de693fd84b0b381f..581e2af9f55cbbaab864af730dfd40a30d11bfff 100644 (file)
@@ -402,6 +402,7 @@ int roomno;
 
     if ((priest = findpriest((char) roomno)) != 0) {
         /* tended */
+        record_achievement(ACH_TMPL);
 
         epri_p = EPRI(priest);
         shrined = has_shrine(priest);
index 595922885ecfac0b77aaf3204fb84f851925999c..8d022388be31f90cfab5e95eae7c0d139048e9b4 100644 (file)
@@ -525,6 +525,8 @@ struct monst *oracl;
     }
     money2mon(oracl, (long) u_pay);
     g.context.botl = 1;
+    if (!u.uevent.major_oracle && !u.uevent.minor_oracle)
+        record_achievement(ACH_ORCL);
     add_xpts = 0; /* first oracle of each type gives experience points */
     if (u_pay == minor_cost) {
         outrumor(1, BY_ORACLE);
index 05aad8291bd0c01ac74e7c4ace3999a680d4742e..bd91495d4942a2239b4c6c80c122f5599244d085 100644 (file)
--- a/src/shk.c
+++ b/src/shk.c
@@ -564,6 +564,7 @@ char *enterstring;
         u.ushops[0] = '\0';
         return;
     }
+    record_achievement(ACH_SHOP);
 
     eshkp->bill_p = &(eshkp->bill[0]);
 
index 11718e3c007cb6085ba405dc0db0330c021752f1..d100789cc6e556a709359293c5aa6cc0f8bac805 100644 (file)
@@ -465,7 +465,8 @@ register struct obj *spellbook;
         }
     }
 
-    if (g.context.spbook.delay && !confused && spellbook == g.context.spbook.book
+    if (g.context.spbook.delay && !confused
+        && spellbook == g.context.spbook.book
         /* handle the sequence: start reading, get interrupted, have
            g.context.spbook.book become erased somehow, resume reading it */
         && booktype != SPE_BLANK_PAPER) {
@@ -490,6 +491,7 @@ register struct obj *spellbook;
                 check_unpaid(spellbook);
                 makeknown(booktype);
                 if (!u.uevent.read_tribute) {
+                    record_achievement(ACH_NOVL);
                     /* give bonus of 20 xp and 4*20+0 pts */
                     more_experienced(20, 0);
                     newexplevel();
index 1581cd4dc18fa93ae7661e60035e2bf917b07fb1..c1ef77f32bfc53f6395ad41b830b9dea69c5fded 100644 (file)
@@ -70,7 +70,7 @@ static void FDECL(writeentry, (FILE *, struct toptenentry *));
 static void FDECL(writexlentry, (FILE *, struct toptenentry *, int));
 static long NDECL(encodexlogflags);
 static long NDECL(encodeconduct);
-static long NDECL(encodeachieve);
+static long FDECL(encodeachieve, (BOOLEAN_P));
 #endif
 static void FDECL(free_ttlist, (struct toptenentry *));
 static int FDECL(classmon, (char *, BOOLEAN_P));
@@ -365,7 +365,8 @@ int how;
         Fprintf(rfile, "%cwhile=%s", XLOG_SEP,
                 g.multi_reason ? g.multi_reason : "helpless");
     Fprintf(rfile, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP,
-            encodeconduct(), XLOG_SEP, g.moves, XLOG_SEP, encodeachieve());
+            encodeconduct(), XLOG_SEP, g.moves, XLOG_SEP,
+            encodeachieve(FALSE));
     Fprintf(rfile, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP,
             (long) urealtime.realtime, XLOG_SEP,
             (long) ubirthday, XLOG_SEP, (long) urealtime.finish_time);
@@ -426,54 +427,26 @@ encodeconduct()
 }
 
 static long
-encodeachieve()
+encodeachieve(secondlong)
+boolean secondlong; /* False: handle achievements 1..31, True: 32..62 */
 {
-    int i, ilimit;
+    int i, achidx, offset;
     long r = 0L;
 
     /*
-     * Other potential achievements to track (some already recorded
-     * for other purposes such as automatic level annotations or
-     * quest progress):
-     *  entered mines branch,
-     *  chatted with the Oracle,
-     *  read a Discworld novel,
-     *  entered Sokoban's first level,
-     *  entered Bigroom level,
-     *  got quest summons,
-     *  entered quest branch,
-     *  chatted with leader,
-     *  entered second quest level,
-     *  entered last quest level,
-     *  defeated nemesis (not same as acquiring Bell or artifact),
-     *  completed quest (again, not the same as getting the items),
-     *  entered rogue level,
-     *  entered Fort Ludios level/branch,
-     *  entered Medusa level,
-     *  entered castle level,
-     *  opened castle drawbridge,
-     *  obtained castle wand,
-     *  entered valley level,
-     *  [assorted demon lairs?],
-     *  entered Vlad's tower branch,
-     *  defeated Vlad (not same as acquiring Candelabrum),
-     *  entered Wizard's tower area within relevant level,
-     *  defeated Wizard,
-     *  found vibrating square,
-     *  entered sanctum level,
-     *  [defeated Riders],
-     *  located the correct high altar (for initial alignment or current
-     *   one or both?) on the astral level.
-     * Too many to include them all in a 32-bit mask, but that could
-     * handle a lot of them.  Defeated <foo> can be seen via the vanquished
-     * monsters list and many or all of the entered <bar> can be seen via
-     * the dungeon overview but both of those things go away as soon as
-     * the program exits.
+     * 32: portable limit for 'long'.
+     * Force 32 even on configurations that are using 64 bit longs.
+     *
+     * We use signed long and limit ourselves to 31 bits since tools
+     * that post-process xlogfile might not be able to cope with
+     * 'unsigned long'.
      */
-    ilimit = min(N_ACH, 32 - 1); /* 32: portable limit for 'long' */
-    for (i = 0; i < ilimit && u.uachieved[i]; ++i)
-        r |= 1L << (u.uachieved[i] - 1);
-
+    offset = secondlong ? (32 - 1) : 0;
+    for (i = 0; u.uachieved[i]; ++i) {
+        achidx = u.uachieved[i] - offset;
+        if (achidx > 0 && achidx < 32) /* value 1..31 sets bit 0..30 */
+            r |= 1L << (achidx - 1);
+    }
     return r;
 }