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

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