add some new, easier achievements

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.
This commit is contained in:
PatR
2020-02-12 14:35:37 -08:00
parent 1ec6e6f96b
commit 804499d9be
15 changed files with 195 additions and 88 deletions

View 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

View 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.

View 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 {

View 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 \

View 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 */

View File

@@ -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;
}
}

View File

@@ -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);

View 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;
}

View 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... */

View 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;
}

View 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);

View 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);

View File

@@ -564,6 +564,7 @@ char *enterstring;
u.ushops[0] = '\0';
return;
}
record_achievement(ACH_SHOP);
eshkp->bill_p = &(eshkp->bill[0]);

View 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();

View 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;
}