From: PatR Date: Mon, 4 May 2020 23:35:40 +0000 (-0700) Subject: track eight more achievements X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=116642ce1e74cfeb0e34ec78ca37a4c979d7353a;p=nethack track eight more achievements Record reaching experience level 3, 6, 10, 14, 18, 22, 26, and 30, the levels where the character gets a new rank title, and report those as achievements at end of game. These achievements persist even if enough levels to lose a rank are lost, and if lost ranks are regained the original achievement is the one that gets tracked and disclosed. --- diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index bc0d48031..b10e8ca25 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -2876,7 +2876,7 @@ attained. They aren't directly related to \fIconduct\fP but are grouped with it because they fall into the same category of \(lqbragging rights\(rq and to limit the number of questions during disclosure. -Listed roughly in order of difficulty and not necessarily in the order +Listed here roughly in order of difficulty and not necessarily in the order in which you might accomplish them. .\" Vary the output between Guidebook.txt and Guidebook.{ps,pdf} .ie \n(fF \{\ @@ -2891,6 +2891,8 @@ in which you might accomplish them. .PS "Mines'\~End\~" .\} .fi +.PL "" +Attained rank title . .PL Shop Entered a shop. .PL Temple @@ -2953,6 +2955,15 @@ Delivered the Amulet to its final destination. .sp .lp "Notes: " .pg +Achievements are recorded and subsequently reported in the order in which +they happen during your current game rather than the order listed here. +.pg +There are nine \fI\fP titles for each role, bestowed at experience +levels 1, 3, 6, 10, 14, 18, 22, 26, and 30. +The one for experience level 1 is not recorded as an achievement. +Losing enough levels to revert to lower rank(s) does not discard the +corresponding achievement(s). +.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. diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index da0f1f3c3..937000f8f 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -3107,7 +3107,7 @@ attained. They aren't directly related to {\it conduct\/} but are grouped with it because they fall into the same category of ``bragging rights'' and to limit the number of questions during disclosure. -Listed roughly in order of difficulty and not necessarily in the order +Listed here roughly in order of difficulty and not necessarily in the order in which you might accomplish them. % [length stuff copied from paranoid_confirmation] @@ -3117,6 +3117,8 @@ in which you might accomplish them. \addtolength{\achwidth}{\labelsep} \blist{\leftmargin \achwidth \topsep 1mm \itemsep 0mm} %.PL Shop +\item[{\tt }] +Attained rank title . \item[{\tt Shop}] Entered a shop. \item[{\tt Temple}] @@ -3169,6 +3171,17 @@ Delivered the Amulet to its final destination. \noindent Notes: +%.pg +Achievements are recorded and subsequently reported in the order in which +they happen during your current game rather than the order listed here. + +%.pg +There are nine {\it \/} titles for each role, bestowed at experience +levels 1, 3, 6, 10, 14, 18, 22, 26, and 30. +The one for experience level 1 is not recorded as an achievement. +Losing enough levels to revert to lower rank(s) does not discard the +corresponding achievement(s). + %.pg The ``special items'' hidden in {\it Mines'~End\/} and (\it Sokoban\/} are not unique but are considered to be prizes or rewards diff --git a/include/extern.h b/include/extern.h index f41f9531a..4b5bac169 100644 --- a/include/extern.h +++ b/include/extern.h @@ -170,6 +170,7 @@ E char *NDECL(do_statusline2); E void NDECL(bot); E void NDECL(timebot); E int FDECL(xlev_to_rank, (int)); +E int FDECL(rank_to_xlev, (int)); E const char *FDECL(rank_of, (int, SHORT_P, BOOLEAN_P)); E int FDECL(title_to_mon, (const char *, int *, int *)); E void NDECL(max_rank_sz); @@ -1007,9 +1008,10 @@ E void FDECL(youhiding, (BOOLEAN_P, int)); E char *FDECL(trap_predicament, (char *, int, BOOLEAN_P)); E int NDECL(doconduct); E void FDECL(show_conduct, (int)); -E void FDECL(record_achievement, (XCHAR_P)); -E boolean FDECL(remove_achievement, (XCHAR_P)); +E void FDECL(record_achievement, (SCHAR_P)); +E boolean FDECL(remove_achievement, (SCHAR_P)); E int NDECL(count_achievements); +E schar FDECL(achieve_rank, (int)); E int NDECL(dovanquished); E int NDECL(doborn); E void FDECL(list_vanquished, (CHAR_P, BOOLEAN_P)); diff --git a/include/you.h b/include/you.h index d17d65ba2..437500d9c 100644 --- a/include/you.h +++ b/include/you.h @@ -93,14 +93,15 @@ enum achivements { 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 */ + /* 23..30 are negated if hero is female at the time new rank is gained */ + ACH_RNK1 = 23, ACH_RNK2 = 24, ACH_RNK3 = 25, ACH_RNK4 = 26, + ACH_RNK5 = 27, ACH_RNK6 = 28, ACH_RNK7 = 29, ACH_RNK8 = 30, + /* foo=31, 1 available potential achievement; #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, @@ -456,7 +457,7 @@ struct you { struct skills weapon_skills[P_NUM_SKILLS]; boolean twoweap; /* KMH -- Using two-weapon combat */ short mcham; /* vampire mndx if shapeshifted to bat/cloud */ - xchar uachieved[N_ACH]; /* list of achievements in the order attained */ + schar uachieved[N_ACH]; /* list of achievements in the order attained */ }; /* end of `struct you' */ #define Upolyd (u.umonnum != u.umonster) diff --git a/src/botl.c b/src/botl.c index 06f8a3555..2dfaa11b9 100644 --- a/src/botl.c +++ b/src/botl.c @@ -270,18 +270,37 @@ int xlev_to_rank(xlev) int xlev; { + /* + * 1..2 => 0 + * 3..5 => 1 + * 6..9 => 2 + * 10..13 => 3 + * ... + * 26..29 => 7 + * 30 => 8 + * Conversion is precise but only partially reversible. + */ return (xlev <= 2) ? 0 : (xlev <= 30) ? ((xlev + 2) / 4) : 8; } -#if 0 /* not currently needed */ /* convert rank index (0..8) to experience level (1..30) */ int rank_to_xlev(rank) int rank; { - return (rank <= 0) ? 1 : (rank <= 8) ? ((rank * 4) - 2) : 30; + /* + * 0 => 1..2 + * 1 => 3..5 + * 2 => 6..9 + * 3 => 10..13 + * ... + * 7 => 26..29 + * 8 => 30 + * We return the low end of each range. + */ + return (rank < 1) ? 1 : (rank < 2) ? 3 + : (rank < 8) ? ((rank * 4) - 2) : 30; } -#endif const char * rank_of(lev, monnum, female) diff --git a/src/exper.c b/src/exper.c index 8c4464511..f7e426db4 100644 --- a/src/exper.c +++ b/src/exper.c @@ -299,9 +299,12 @@ boolean incr; /* true iff via incremental experience growth */ /* increase level (unless already maxxed) */ if (u.ulevel < MAXULEV) { + int newrank, oldrank = xlev_to_rank(u.ulevel); + /* increase experience points to reflect new level */ if (incr) { long tmp = newuexp(u.ulevel + 1); + if (u.uexp >= tmp) u.uexp = tmp - 1; } else { @@ -314,6 +317,9 @@ boolean incr; /* true iff via incremental experience growth */ if (u.ulevelmax < u.ulevel) u.ulevelmax = u.ulevel; adjabil(u.ulevel - 1, u.ulevel); /* give new intrinsics */ + newrank = xlev_to_rank(u.ulevel); + if (newrank > oldrank) + record_achievement(achieve_rank(newrank)); } g.context.botl = TRUE; } diff --git a/src/insight.c b/src/insight.c index a44e04bc6..6e300241b 100644 --- a/src/insight.c +++ b/src/insight.c @@ -1882,8 +1882,8 @@ static void show_achievements(final) int final; /* used "behind the curtain" by enl_foo() macros */ { - int i, achidx, acnt; - char title[BUFSZ]; + int i, achidx, absidx, acnt; + char title[QBUFSZ], buf[QBUFSZ]; winid awin = WIN_ERR; /* unfortunately we can't show the achievements (at least not all of @@ -1922,8 +1922,9 @@ int final; /* used "behind the curtain" by enl_foo() macros */ } for (i = 0; i < acnt; ++i) { achidx = u.uachieved[i]; + absidx = abs(achidx); - switch (achidx) { + switch (absidx) { case ACH_BLND: enl_msg(You_, "are exploring", "explored", " without being able to see", ""); @@ -2013,10 +2014,19 @@ int final; /* used "behind the curtain" by enl_foo() macros */ /* the ultimate achievement... */ enlght_out(" You ascended!"); break; + + /* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */ + case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4: + case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8: + Sprintf(buf, "attained the rank of %s", + rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)), + Role_switch, (achidx < 0) ? TRUE : FALSE)); + you_have_X(buf); + break; + default: - /* title[] has served its purpose, reuse it as a scratch buffer */ - Sprintf(title, " [Unexpected achievement #%d.]", achidx); - enlght_out(title); + Sprintf(buf, " [Unexpected achievement #%d.]", achidx); + enlght_out(buf); break; } /* switch */ } /* for */ @@ -2030,12 +2040,15 @@ int final; /* used "behind the curtain" by enl_foo() macros */ /* record an achievement (add at end of list unless already present) */ void record_achievement(achidx) -xchar achidx; +schar achidx; { - int i; + int i, absidx; - /* valid achievements range from 1 to N_ACH-1 */ - if (achidx < 1 || achidx >= N_ACH) { + absidx = abs(achidx); + /* valid achievements range from 1 to N_ACH-1; however, ranks can be + stored as the complement (ie, negative) to track gender */ + if ((achidx < 1 && (absidx < ACH_RNK1 || absidx > ACH_RNK8)) + || achidx >= N_ACH) { impossible("Achievement #%d is out of range.", achidx); return; } @@ -2046,7 +2059,7 @@ xchar achidx; an attempt to duplicate an achievement can happen if any of Bell, Candelabrum, Book, or Amulet is dropped then picked up again */ for (i = 0; u.uachieved[i]; ++i) - if (u.uachieved[i] == achidx) + if (abs(u.uachieved[i]) == abs(achidx)) return; /* already recorded, don't duplicate it */ u.uachieved[i] = achidx; return; @@ -2055,12 +2068,12 @@ xchar achidx; /* discard a recorded achievement; return True if removed, False otherwise */ boolean remove_achievement(achidx) -xchar achidx; +schar achidx; { int i; for (i = 0; u.uachieved[i]; ++i) - if (u.uachieved[i] == achidx) + if (abs(u.uachieved[i]) == abs(achidx)) break; /* stop when found */ if (!u.uachieved[i]) /* not found */ return FALSE; @@ -2082,6 +2095,19 @@ count_achievements() return acnt; } +/* convert a rank index to an achievement number; encode it when female + in order to subsequently report gender-specific ranks accurately */ +schar +achieve_rank(rank) +int rank; /* 1..8 */ +{ + schar achidx = (schar) ((rank - 1) + ACH_RNK1); + + if (flags.female) + achidx = -achidx; + return achidx; +} + /* * Vanquished monsters. */ diff --git a/src/topten.c b/src/topten.c index 2e1cc7361..c86f38ab8 100644 --- a/src/topten.c +++ b/src/topten.c @@ -474,13 +474,16 @@ boolean condition; static char * encode_extended_achievements() { - static char buf[N_ACH*40]; + static char buf[N_ACH * 40]; + char rnkbuf[40]; const char *achievement = NULL; - int i; + int i, achidx, absidx; buf[0] = '\0'; for (i = 0; u.uachieved[i]; i++) { - switch (u.uachieved[i]) { + achidx = u.uachieved[i]; + absidx = abs(achidx); + switch (absidx) { case ACH_UWIN: achievement = "ascended"; break; @@ -517,7 +520,6 @@ encode_extended_achievements() case ACH_SOKO_PRIZE: achievement = "obtained_the_sokoban_prize"; break; - case ACH_ORCL: achievement = "consulted_the_oracle"; break; @@ -542,6 +544,15 @@ encode_extended_achievements() case ACH_BGRM: achievement = "entered_bigroom"; break; + /* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */ + case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4: + case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8: + Sprintf(rnkbuf, "attained_the_rank_of_%s", + rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)), + Role_switch, (achidx < 0) ? TRUE : FALSE)); + strNsubst(rnkbuf, " ", "_", 0); /* replace every ' ' with '_' */ + achievement = lcase(rnkbuf); + break; default: continue; }