From 804499d9bed9733096e70b550768ddad5feb0888 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 12 Feb 2020 14:35:37 -0800 Subject: [PATCH] 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. --- doc/Guidebook.mn | 31 +++++++++++++++++---- doc/Guidebook.tex | 23 +++++++++++++--- include/context.h | 6 +++-- include/patchlevel.h | 2 +- include/you.h | 64 +++++++++++++++++++++++++++++++++++++++----- src/cmd.c | 8 +++--- src/do.c | 22 ++++++++++----- src/hack.c | 13 ++++++--- src/insight.c | 39 +++++++++++++++++++++------ src/invent.c | 4 +-- src/priest.c | 1 + src/rumors.c | 2 ++ src/shk.c | 1 + src/spell.c | 4 ++- src/topten.c | 63 +++++++++++++------------------------------ 15 files changed, 195 insertions(+), 88 deletions(-) diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index faf3a992c..f79c5ad3c 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -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 diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 0340cc036..a5f44543f 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -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. diff --git a/include/context.h b/include/context.h index a872fa405..e2bd9b49f 100644 --- a/include/context.h +++ b/include/context.h @@ -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 { diff --git a/include/patchlevel.h b/include/patchlevel.h index 3869fbe32..7be0bce3f 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -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 \ diff --git a/include/you.h b/include/you.h index f017f0e0c..b51c34c67 100644 --- a/include/you.h +++ b/include/you.h @@ -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 " 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 */ diff --git a/src/cmd.c b/src/cmd.c index 94d5e245b..379054f44 100644 --- 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; } } diff --git a/src/do.c b/src/do.c index 70d51f8da..f7d60eec9 100644 --- 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); diff --git a/src/hack.c b/src/hack.c index 84639f63e..18c0157ea 100644 --- a/src/hack.c +++ b/src/hack.c @@ -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; } diff --git a/src/insight.c b/src/insight.c index 8ecc3b434..c368c4403 100644 --- a/src/insight.c +++ b/src/insight.c @@ -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... */ diff --git a/src/invent.c b/src/invent.c index 51568945e..3c0347ad9 100644 --- a/src/invent.c +++ b/src/invent.c @@ -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; } diff --git a/src/priest.c b/src/priest.c index 259597e7d..581e2af9f 100644 --- a/src/priest.c +++ b/src/priest.c @@ -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); diff --git a/src/rumors.c b/src/rumors.c index 595922885..8d022388b 100644 --- a/src/rumors.c +++ b/src/rumors.c @@ -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); diff --git a/src/shk.c b/src/shk.c index 05aad8291..bd91495d4 100644 --- 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]); diff --git a/src/spell.c b/src/spell.c index 11718e3c0..d100789cc 100644 --- a/src/spell.c +++ b/src/spell.c @@ -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(); diff --git a/src/topten.c b/src/topten.c index 1581cd4dc..c1ef77f32 100644 --- a/src/topten.c +++ b/src/topten.c @@ -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 can be seen via the vanquished - * monsters list and many or all of the entered 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; } -- 2.40.0