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.
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
.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.
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.
.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
%.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.
\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.
};
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 {
* 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 \
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 */
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;
}
}
/* 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)) {
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
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);
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
/* possibly deliver a one-time room entry message */
void
check_special_room(newlev)
-register boolean newlev;
+boolean newlev;
{
register struct monst *mtmp;
char *ptr;
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);
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...");
break;
case DELPHI: {
struct monst *oracle = monstinroom(&mons[PM_ORACLE], roomno);
+
if (oracle) {
if (!oracle->mpeaceful)
verbalize("You're in Delphi, %s.", g.plname);
}
}
}
-
return;
}
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
" 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
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... */
/* "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;
}
if ((priest = findpriest((char) roomno)) != 0) {
/* tended */
+ record_achievement(ACH_TMPL);
epri_p = EPRI(priest);
shrined = has_shrine(priest);
}
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);
u.ushops[0] = '\0';
return;
}
+ record_achievement(ACH_SHOP);
eshkp->bill_p = &(eshkp->bill[0]);
}
}
- 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) {
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();
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));
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);
}
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;
}