redo achievement tracking

Instead of an assortment of bits, assign numeric indices to the
potential achievements and keep an array of those in the order they
were attained.  So disclosure might show the same subset occurring
differently in different games depending on the player's actions.
The encoded field in xlogfile doesn't care about that and remains
the same.

Modifies 'struct u', so EDITLEVEL has been incremented and existing
save files are invalidated.
This commit is contained in:
PatR
2020-02-10 00:17:54 -08:00
parent 89c1b09d70
commit d462bdffca
12 changed files with 238 additions and 177 deletions

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 cmd.c $NHDT-Date: 1579914040 2020/01/25 01:00:40 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.394 $ */
/* NetHack 3.6 cmd.c $NHDT-Date: 1581322659 2020/02/10 08:17:39 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.398 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2013. */
/* NetHack may be freely redistributed. See license for details. */
@@ -792,16 +792,12 @@ boolean pre, wiztower;
static const char Unachieve[] = "%s achievement revoked.";
if (Is_mineend_level(&u.uz)) {
if (u.uachieve.mines_luckstone) {
if (remove_achievement(ACH_LUCK))
pline(Unachieve, "Mine's end");
u.uachieve.mines_luckstone = 0;
}
g.context.achieveo.mines_prize_oid = 0;
} else if (Is_sokoend_level(&u.uz)) {
if (u.uachieve.finish_sokoban) {
if (remove_achievement(ACH_SOKO))
pline(Unachieve, "Sokoban end");
u.uachieve.finish_sokoban = 0;
}
g.context.achieveo.soko_prize_oid = 0;
}
}

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 do.c $NHDT-Date: 1580608377 2020/02/02 01:52:57 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.222 $ */
/* NetHack 3.6 do.c $NHDT-Date: 1581322660 2020/02/10 08:17:40 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.224 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1039,8 +1039,7 @@ dodown()
pline("Unspeakable cruelty and harm lurk down there.");
if (yn("Are you sure you want to enter?") != 'y')
return 0;
else
pline("So be it.");
pline("So be it.");
u.uevent.gehennom_entered = 1; /* don't ask again */
}
@@ -1642,7 +1641,8 @@ boolean at_stairs, falling, portal;
You_hear("groans and moans everywhere.");
} else
pline("It is hot here. You smell smoke...");
u.uachieve.enter_gehennom = 1;
record_achievement(ACH_HELL); /* reached Gehennom */
}
/* in case we've managed to bypass the Valley's stairway down */
if (Inhell && !Is_valley(&u.uz))
@@ -1677,10 +1677,14 @@ boolean at_stairs, falling, portal;
/* special location arrival messages/events */
if (In_endgame(&u.uz)) {
if (new &&on_level(&u.uz, &astral_level))
if (newdungeon)
record_achievement(ACH_ENDG); /* reached endgame */
if (new && on_level(&u.uz, &astral_level)) {
final_level(); /* guardian angel,&c */
else if (newdungeon && u.uhave.amulet)
record_achievement(ACH_ASTR); /* reached Astral level */
} else if (newdungeon && u.uhave.amulet) {
resurrect(); /* force confrontation with Wizard */
}
} else if (In_quest(&u.uz)) {
onquest(); /* might be reaching locate|goal level */
} else if (In_V_tower(&u.uz)) {

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 end.c $NHDT-Date: 1575245059 2019/12/02 00:04:19 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.181 $ */
/* NetHack 3.6 end.c $NHDT-Date: 1581322661 2020/02/10 08:17:41 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.206 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
@@ -812,7 +812,7 @@ boolean taken;
if (!done_stopprint) {
if (should_query_disclose_option('c', &defquery)) {
int acnt = count_uachieve();
int acnt = count_achievements();
Sprintf(qbuf, "Do you want to see your conduct%s%s?",
(acnt > 0) ? " and achievement" : "",
@@ -1222,6 +1222,17 @@ int how;
iflags.at_night = night();
iflags.at_midnight = midnight();
/* final achievement tracking; only show blind and nudist if some
tangible progress has been made; always show ascension last */
if (u.uachieved[0] || !flags.beginner) {
if (u.uroleplay.blind)
record_achievement(ACH_BLND); /* blind the whole game */
if (u.uroleplay.nudist)
record_achievement(ACH_NUDE); /* never wore armor */
}
if (how == ASCENDED)
record_achievement(ACH_UWIN);
dump_open_log(endtime);
/* Sometimes you die on the first move. Life's not fair.
* On those rare occasions you get hosed immediately, go out
@@ -1592,6 +1603,7 @@ int how;
* score list?" */
if (have_windows && !iflags.toptenwin)
exit_nhwindows((char *) 0), have_windows = FALSE;
/* update 'logfile' and 'xlogfile', if enabled, and maybe 'record' */
topten(how, endtime);
if (have_windows)
exit_nhwindows((char *) 0);

View File

@@ -1,4 +1,4 @@
/* NetHack 3.7 insight.c $NHDT-Date: 1580577249 2020/02/01 17:14:09 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.0 $ */
/* NetHack 3.7 insight.c $NHDT-Date: 1581322662 2020/02/10 08:17:42 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1698,51 +1698,17 @@ int final;
g.en_win = WIN_ERR;
}
/* uses to decide whether there are any achievements to display */
int
count_uachieve()
{
int acnt = 0;
/* these tests must be kept in sync with show_achievements() */
if (u.uroleplay.blind)
++acnt;
if (u.uroleplay.nudist)
++acnt;
if (u.uachieve.mines_luckstone)
++acnt;
if (u.uachieve.finish_sokoban)
++acnt;
if (u.uachieve.killed_medusa)
++acnt;
if (u.uachieve.bell)
++acnt;
if (u.uachieve.enter_gehennom)
++acnt;
if (u.uachieve.menorah)
++acnt;
if (u.uachieve.book)
++acnt;
if (u.uevent.invoked)
++acnt;
if (u.uachieve.amulet)
++acnt;
if (In_endgame(&u.uz))
++acnt;
if (Is_astralevel(&u.uz))
++acnt;
if (u.uachieve.ascended)
++acnt;
return acnt;
}
/*
* Achievements (see 'enum achievements' in you.h).
*/
static void
show_achievements(final)
int final;
int final; /* used "behind the curtain" by enl_foo() macros */
{
int acnt;
int i, achidx, acnt;
char title[BUFSZ];
boolean ach_amulet = FALSE;
winid awin = WIN_ERR;
/* unfortunately we can't show the achievements (at least not all of
@@ -1754,7 +1720,7 @@ int final;
/* first, figure whether any achievements have been accomplished
so that we don't show the header for them if the resulting list
below it would be empty */
if ((acnt = count_uachieve()) == 0)
if ((acnt = count_achievements()) == 0)
return;
if (g.en_win != WIN_ERR) {
@@ -1765,67 +1731,105 @@ int final;
}
Sprintf(title, "Achievement%s:", plur(acnt));
putstr(awin, 0, title);
/* after 'blind' and 'nudist', which are the easiest if you die but
the hardest if you ascend, they're arranged in approximate order
of difficulty */
if (u.uroleplay.blind)
enl_msg(You_, "are exploring", "explored",
" without being able to see", "");
if (u.uroleplay.nudist)
enl_msg(You_, "have gone", "went", " without any armor", "");
if (u.uachieve.mines_luckstone)
enl_msg(You_, "have ", "", "completed the Gnomish Mines", "");
if (u.uachieve.finish_sokoban)
enl_msg(You_, "have ", "", "completed Sokoban", "");
if (u.uachieve.killed_medusa)
enl_msg(You_, "have ", "", "defeated Medusa", "");
if (u.uachieve.bell) {
/* alternate phrasing for present vs past and also for possessing
the item vs once held it */
enl_msg(You_,
u.uhave.bell ? "have" : "have handled",
u.uhave.bell ? "had" : "handled",
" the Bell of Opening", "");
}
/* wording is clumsy but the game is inconsistent about "entering
Gehennom"; the Valley is part of Gehennom but the message about
entering Gehennom is given when descending from the Valley to the
level below and that's also when the flag about entering gets set */
if (u.uachieve.enter_gehennom)
enl_msg(You_, "have ", "", "passed the Valley of the Dead", "");
if (u.uachieve.menorah) {
enl_msg(You_,
u.uhave.menorah ? "have" : "have handled",
u.uhave.menorah ? "had" : "handled",
" the Candelabrum of Invocation", "");
}
if (u.uachieve.book) {
enl_msg(You_,
u.uhave.book ? "have" : "have handled",
u.uhave.book ? "had" : "handled",
" the Book of the Dead", "");
}
if (u.uevent.invoked)
enl_msg(You_, "have ", "", "gained access to Moloch's Sanctum", "");
if (u.uachieve.amulet) {
/* extra alternate wording for past tense because ascension
requires giving up the Amulet */
enl_msg(You_,
u.uhave.amulet ? "have" : "have obtained",
u.uachieve.ascended ? "delivered"
: u.uhave.amulet ? "had" : "had obtained",
" the Amulet of Yendor", "");
/* display achievements in the order in which they were recorded;
lone exception is to defer the Amulet (by taking it out of list)
if we just ascended; it warrants alternate wording when given
away during ascension, but the Amulet achievement is always
attained before entering endgame and the alternate wording looks
strange if shown before "reached endgame" and "reached Astral" */
if (remove_achievement(ACH_UWIN)) { /* UWIN == Ascended! */
ach_amulet = remove_achievement(ACH_AMUL);
record_achievement(ACH_UWIN); /* put back; always last when present */
acnt = count_achievements();
}
for (i = 0; i < acnt; ++i) {
achidx = u.uachieved[i];
/* reaching Astral makes feedback about reaching the Planes be redundant
and asceding makes both be redundant, but we display all that apply */
if (In_endgame(&u.uz))
enl_msg(You_, "have ", "", "reached the Elemental Planes", "");
if (Is_astralevel(&u.uz))
enl_msg(You_, "have ", "", "reached the Astral Plane", "");
if (u.uachieve.ascended)
enlght_out(" You ascended!");
switch (achidx) {
case ACH_BLND:
enl_msg(You_, "are exploring", "explored",
" without being able to see", "");
break;
case ACH_NUDE:
enl_msg(You_, "have gone", "went", " without any armor", "");
break;
case ACH_LUCK:
enl_msg(You_, "have ", "", "completed the Gnomish Mines", "");
break;
case ACH_SOKO:
enl_msg(You_, "have ", "", "completed Sokoban", "");
break;
case ACH_MEDU:
enl_msg(You_, "have ", "", "defeated Medusa", "");
break;
case ACH_BELL:
/* alternate phrasing for present vs past and also for
possessing the item vs once held it */
enl_msg(You_,
u.uhave.bell ? "have" : "have handled",
u.uhave.bell ? "had" : "handled",
" the Bell of Opening", "");
break;
case ACH_HELL:
enl_msg(You_, "have ", "", "entered Gehennom", "");
break;
case ACH_CNDL:
enl_msg(You_,
u.uhave.menorah ? "have" : "have handled",
u.uhave.menorah ? "had" : "handled",
" the Candelabrum of Invocation", "");
break;
case ACH_BOOK:
enl_msg(You_,
u.uhave.book ? "have" : "have handled",
u.uhave.book ? "had" : "handled",
" the Book of the Dead", "");
break;
case ACH_INVK:
enl_msg(You_, "have ", "",
"gained access to Moloch's Sanctum", "");
break;
case ACH_AMUL:
/* note: we won't get here if ACH_UWIN is going to be shown */
enl_msg(You_,
u.uhave.amulet ? "have" : "have obtained",
u.uhave.amulet ? "had" : "had obtained",
" the Amulet of Yendor", "");
break;
/* reaching Astral makes feedback about reaching the Planes
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", "");
break;
case ACH_ASTR:
enl_msg(You_, "have ", "", "reached the Astral Plane", "");
break;
case ACH_UWIN:
/* if we took Amulet achievement out of the list, show it now;
always uses past tense here since game ends upon ascension */
if (ach_amulet)
enl_msg(You_, "?", "delivered", " the Amulet of Yendor", "");
/* the ultimate achievement... */
enlght_out(" You ascended!");
break;
default:
/* title[] has served its purpose, reuse it as a scratch buffer */
Sprintf(title, " [Unexpected achievement #%d.]", achidx);
enlght_out(title);
break;
} /* switch */
} /* for */
#ifdef XLOGFILE
/* if we removed the Amulet achievement because of ascension, put it
back for encoding in the achievements field of xlogfile; it will
change position from the original ordering but that doesn't matter
to the bitmask which is going to be constructed and logged */
if (ach_amulet)
record_achievement(ACH_AMUL);
#endif
if (awin != g.en_win) {
display_nhwindow(awin, TRUE);
@@ -1833,6 +1837,65 @@ int final;
}
}
/* record an achievement (add at end of list unless already present) */
void
record_achievement(achidx)
xchar achidx;
{
int i;
/* valid achievements range from 1 to N_ACH-1 */
if (achidx < 1 || achidx >= N_ACH) {
impossible("Achievement #%d is out of range.", achidx);
return;
}
/* the list has an extra slot so there is always at least one 0 at
its end (more than one unless all N_ACH-1 possible achievements
have been recorded); find first empty slot or achievement #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)
return; /* already recorded, don't duplicate it */
u.uachieved[i] = achidx;
return;
}
/* discard a recorded achievement; return True if removed, False otherwise */
boolean
remove_achievement(achidx)
xchar achidx;
{
int i;
for (i = 0; u.uachieved[i]; ++i)
if (u.uachieved[i] == achidx)
break; /* stop when found */
if (!u.uachieved[i]) /* not found */
return FALSE;
/* list is 0 terminated so any beyond the removed one move up a slot */
do {
u.uachieved[i] = u.uachieved[i + 1];
} while (u.uachieved[++i]);
return TRUE;
}
/* used to decide whether there are any achievements to display */
int
count_achievements()
{
int i, acnt = 0;
for (i = 0; u.uachieved[i]; ++i)
++acnt;
return acnt;
}
/*
* Vanquished monsters.
*/
static const char *vanqorders[NUM_VANQ_ORDER_MODES] = {
"traditional: by monster level, by internal monster index",
"by monster toughness, by internal monster index",

View File

@@ -1,4 +1,4 @@
/* NetHack 3.7 invent.c $NHDT-Date: 1580476196 2020/01/31 13:09:56 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.288 $ */
/* NetHack 3.7 invent.c $NHDT-Date: 1581322662 2020/02/10 08:17:42 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.290 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
@@ -810,22 +810,22 @@ struct obj *obj;
if (u.uhave.amulet)
impossible("already have amulet?");
u.uhave.amulet = 1;
u.uachieve.amulet = 1;
record_achievement(ACH_AMUL);
} else if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
if (u.uhave.menorah)
impossible("already have candelabrum?");
u.uhave.menorah = 1;
u.uachieve.menorah = 1;
record_achievement(ACH_CNDL);
} else if (obj->otyp == BELL_OF_OPENING) {
if (u.uhave.bell)
impossible("already have silver bell?");
u.uhave.bell = 1;
u.uachieve.bell = 1;
record_achievement(ACH_BELL);
} else if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
if (u.uhave.book)
impossible("already have the book?");
u.uhave.book = 1;
u.uachieve.book = 1;
record_achievement(ACH_BOOK);
} else if (obj->oartifact) {
if (is_quest_artifact(obj)) {
if (u.uhave.questart)
@@ -839,12 +839,12 @@ struct obj *obj;
/* "special achievements"; revealed in end of game disclosure and
dumplog, originally just recorded in XLOGFILE */
if (is_mines_prize(obj)) {
u.uachieve.mines_luckstone = 1;
g.context.achieveo.mines_prize_oid = 0;
record_achievement(ACH_LUCK);
g.context.achieveo.mines_prize_oid = 0; /* done with luckstone o_id */
obj->nomerge = 0;
} else if (is_soko_prize(obj)) {
u.uachieve.finish_sokoban = 1;
g.context.achieveo.soko_prize_oid = 0;
record_achievement(ACH_SOKO);
g.context.achieveo.soko_prize_oid = 0; /* done with bag/amulet o_id */
obj->nomerge = 0;
}
}

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 mon.c $NHDT-Date: 1580044343 2020/01/26 13:12:23 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.320 $ */
/* NetHack 3.6 mon.c $NHDT-Date: 1581322664 2020/02/10 08:17:44 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.321 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
@@ -2065,7 +2065,7 @@ register struct monst *mtmp;
if (mtmp->data->msound == MS_NEMESIS)
nemdead();
if (mtmp->data == &mons[PM_MEDUSA])
u.uachieve.killed_medusa = 1;
record_achievement(ACH_MEDU);
if (glyph_is_invisible(levl[mtmp->mx][mtmp->my].glyph))
unmap_object(mtmp->mx, mtmp->my);
m_detach(mtmp, mptr);

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 pray.c $NHDT-Date: 1579401997 2020/01/19 02:46:37 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.139 $ */
/* NetHack 3.6 pray.c $NHDT-Date: 1581322665 2020/02/10 08:17:45 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.140 $ */
/* Copyright (c) Benson I. Margulies, Mike Stephenson, Steve Linhart, 1989. */
/* NetHack may be freely redistributed. See license for details. */
@@ -1516,7 +1516,6 @@ dosacrifice()
/* The final Test. Did you win? */
if (uamul == otmp)
Amulet_off();
u.uevent.ascended = 1;
if (carried(otmp))
useup(otmp); /* well, it's gone now */
else
@@ -1552,8 +1551,8 @@ dosacrifice()
pline(cloud_of_smoke, hcolor(NH_ORANGE));
done(ESCAPED);
} else { /* super big win */
u.uevent.ascended = 1;
adjalign(10);
u.uachieve.ascended = 1;
pline(
"An invisible choir sings, and you are bathed in radiance...");
godvoice(altaralign, "Mortal, thou hast done well!");
@@ -1564,6 +1563,7 @@ dosacrifice()
flags.female ? "dess" : "");
done(ASCENDED);
}
/*NOTREACHED*/
}
} /* real Amulet */

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 spell.c $NHDT-Date: 1546565814 2019/01/04 01:36:54 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.88 $ */
/* NetHack 3.6 spell.c $NHDT-Date: 1581322667 2020/02/10 08:17:47 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.102 $ */
/* Copyright (c) M. Stephenson 1988 */
/* NetHack may be freely redistributed. See license for details. */
@@ -259,6 +259,7 @@ struct obj *book2;
/* successful invocation */
mkinvokearea();
u.uevent.invoked = 1;
record_achievement(ACH_INVK);
/* in case you haven't killed the Wizard yet, behave as if
you just did */
u.uevent.udemigod = 1; /* wizdead() */

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 topten.c $NHDT-Date: 1579914041 2020/01/25 01:00:41 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.62 $ */
/* NetHack 3.6 topten.c $NHDT-Date: 1581322668 2020/02/10 08:17:48 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.64 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
@@ -428,6 +428,7 @@ encodeconduct()
static long
encodeachieve()
{
int i, ilimit;
long r = 0L;
/*
@@ -469,34 +470,9 @@ encodeachieve()
* the dungeon overview but both of those things go away as soon as
* the program exits.
*/
if (u.uachieve.bell)
r |= 1L << 0;
if (u.uachieve.enter_gehennom)
r |= 1L << 1;
if (u.uachieve.menorah)
r |= 1L << 2;
if (u.uachieve.book)
r |= 1L << 3;
if (u.uevent.invoked)
r |= 1L << 4;
if (u.uachieve.amulet)
r |= 1L << 5;
if (In_endgame(&u.uz))
r |= 1L << 6;
if (Is_astralevel(&u.uz))
r |= 1L << 7;
if (u.uachieve.ascended)
r |= 1L << 8;
if (u.uachieve.mines_luckstone)
r |= 1L << 9;
if (u.uachieve.finish_sokoban)
r |= 1L << 10;
if (u.uachieve.killed_medusa)
r |= 1L << 11;
if (u.uroleplay.blind)
r |= 1L << 12;
if (u.uroleplay.nudist)
r |= 1L << 13;
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);
return r;
}