simplify achievement tracking for special objects

This turned out to be a lot more work than I anticipated, but it is
definitely simpler (other than having #wizmakemap take achievements
away if you replace the level that contains the 'prize', which wasn't
handled before).

I cheated and made Mine's End into a no-bones level because the new
flagging scheme for luckstone, bag, and amulet can't carry over from
one game to another.  It probably should have been no-bones all along.
Sokoban didn't have this issue because it's already no-bones.

Existing save files are invalidated.
This commit is contained in:
PatR
2020-01-24 13:54:23 -08:00
parent dba6c20e2d
commit 0166239a22
12 changed files with 85 additions and 71 deletions

View File

@@ -165,14 +165,10 @@ boolean restore;
if (mnum == PM_DOPPELGANGER && otmp->otyp == CORPSE)
set_corpsenm(otmp, mnum);
}
} else if ((otmp->otyp == iflags.mines_prize_type
&& !Is_mineend_level(&u.uz))
|| ((otmp->otyp == iflags.soko_prize_type1
|| otmp->otyp == iflags.soko_prize_type2)
&& !Is_sokoend_level(&u.uz))) {
/* "special prize" in this game becomes ordinary object
if loaded into another game */
otmp->record_achieve_special = NON_PM;
} else if (is_mines_prize(otmp) || is_soko_prize(otmp)) {
/* achievement tracking; in case prize was moved off its
original level (which is always a no-bones level) */
otmp->nomerge = 0;
} else if (otmp->otyp == AMULET_OF_YENDOR) {
/* no longer the real Amulet */
otmp->otyp = FAKE_AMULET_OF_YENDOR;

View File

@@ -806,6 +806,24 @@ boolean pre, wiztower;
continue;
if (mtmp->isshk)
setpaid(mtmp);
/* achievement tracking */
{
static const char Unachieve[] = "%s achievement revoked.";
if (Is_mineend_level(&u.uz)) {
if (u.uachieve.mines_luckstone) {
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) {
pline(Unachieve, "Sokoban end");
u.uachieve.finish_sokoban = 0;
}
g.context.achieveo.soko_prize_oid = 0;
}
}
/* TODO?
* Reduce 'born' tally for each monster about to be discarded
* by savelev(), otherwise replacing heavily populated levels
@@ -877,6 +895,7 @@ wiz_makemap(VOID_ARGS)
{
if (wizard) {
boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz);
makemap_prepost(TRUE, was_in_W_tower);
/* create a new level; various things like bestowing a guardian
angel on Astral or setting off alarm on Ft.Ludios are handled

View File

@@ -638,8 +638,6 @@ const struct instance_globals g_init = {
UNDEFINED_VALUE, /* ysize */
FALSE, /* splev_init_present */
FALSE, /* icedpools */
0, /* mines_prize_count */
0, /* soki_prize_count */
{ UNDEFINED_PTR }, /* container_obj */
0, /* container_idx */
NULL, /* invent_carrying_monster */

View File

@@ -835,20 +835,14 @@ struct obj *obj;
}
/* "special achievements" aren't discoverable during play, they
end up being recorded in XLOGFILE at end of game, nowhere else;
record_achieve_special overloads corpsenm which is ordinarily
initialized to NON_PM (-1) rather than to 0; any special prize
must never be a corpse, egg, tin, figurine, or statue because
their use of obj->corpsenm for monster type would conflict,
nor be a leash (corpsenm overloaded for m_id of leashed
monster) or a novel (corpsenm overloaded for novel index) */
end up being recorded in XLOGFILE at end of game, nowhere else */
if (is_mines_prize(obj)) {
u.uachieve.mines_luckstone = 1;
obj->record_achieve_special = NON_PM;
g.context.achieveo.mines_prize_oid = 0;
obj->nomerge = 0;
} else if (is_soko_prize(obj)) {
u.uachieve.finish_sokoban = 1;
obj->record_achieve_special = NON_PM;
g.context.achieveo.soko_prize_oid = 0;
obj->nomerge = 0;
}
}

View File

@@ -737,9 +737,9 @@ initoptions_init()
/* for "special achievement" tracking (see obj.h,
create_object(sp_lev.c), addinv_core1(invent.c) */
iflags.mines_prize_type = LUCKSTONE;
iflags.soko_prize_type1 = BAG_OF_HOLDING;
iflags.soko_prize_type2 = AMULET_OF_REFLECTION;
g.context.achieveo.mines_prize_type = LUCKSTONE;
g.context.achieveo.soko_prize_typ1 = BAG_OF_HOLDING;
g.context.achieveo.soko_prize_typ2 = AMULET_OF_REFLECTION;
/* assert( sizeof flags.inv_order == sizeof def_inv_order ); */
(void) memcpy((genericptr_t) flags.inv_order,

View File

@@ -1569,32 +1569,34 @@ struct mkroom *croom;
if (o->id != -1) {
static const char prize_warning[] = "multiple prizes on %s level";
/* if this is a specific item of the right type and it is being
created on the right level, flag it as the designated item
used to detect a special achievement (to whit, reaching and
exploring the target level, although the exploration part
might be short-circuited if a monster brings object to hero) */
if (Is_mineend_level(&u.uz)) {
if (otmp->otyp == iflags.mines_prize_type) {
if (!g.mines_prize_count++) {
/* Note: the first luckstone on lev will become the prize
even if its not the explicit one, but random */
otmp->record_achieve_special = MINES_PRIZE;
/* prevent stacking; cleared when achievement is recorded */
otmp->nomerge = 1;
}
/*
* If this is a specific item of the right type and it is being
* created on the right level, flag it as the designated item
* used to detect a special achievement (to whit, reaching and
* exploring the target level, although the exploration part
* might be short-circuited if a monster brings object to hero).
*
* Random items of the appropriate type won't trigger a false
* match--they'll fail the (id != -1) test above--but the level
* definition should not include a second instance of any prize.
*/
if (Is_mineend_level(&u.uz)
&& otmp->otyp == g.context.achieveo.mines_prize_type) {
if (!g.context.achieveo.mines_prize_oid) {
g.context.achieveo.mines_prize_oid = otmp->o_id;
/* prevent stacking; cleared when achievement is recorded */
otmp->nomerge = 1;
} else {
impossible(prize_warning, "mines end");
}
} else if (Is_sokoend_level(&u.uz)) {
if (otmp->otyp == iflags.soko_prize_type1) {
otmp->record_achieve_special = SOKO_PRIZE1;
} else if (Is_sokoend_level(&u.uz)
&& (otmp->otyp == g.context.achieveo.soko_prize_typ1
|| otmp->otyp == g.context.achieveo.soko_prize_typ2)) {
if (!g.context.achieveo.soko_prize_oid) {
g.context.achieveo.soko_prize_oid = otmp->o_id;
otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack */
if (++g.soko_prize_count > 1)
impossible(prize_warning, "sokoban end");
} else if (otmp->otyp == iflags.soko_prize_type2) {
otmp->record_achieve_special = SOKO_PRIZE2;
otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack */
if (++g.soko_prize_count > 1)
impossible(prize_warning, "sokoban end");
} else {
impossible(prize_warning, "sokoban end");
}
}
}
@@ -2590,14 +2592,26 @@ const char *s;
/* find by object name */
for (i = 0; i < NUM_OBJECTS; i++) {
objname = obj_descr[i].oc_name;
objname = OBJ_NAME(objects[i]);
if (objname && !strcmpi(s, objname))
return i;
}
/*
* FIXME:
* If the file specifies "orange potion", the actual object
* description is just "orange" and won't match. [There's a
* reason that wish handling is insanely complicated.] And
* even if that gets fixed, if the file specifies "gray stone"
* it will start matching but would always pick the first one.
*
* "orange potion" is an unlikely thing to have in a special
* level description but "gray stone" is not....
*/
/* find by object description */
for (i = 0; i < NUM_OBJECTS; i++) {
objname = obj_descr[i].oc_descr;
objname = OBJ_DESCR(objects[i]);
if (objname && !strcmpi(s, objname))
return i;
}
@@ -5419,10 +5433,6 @@ sp_level_coder_init()
g.splev_init_present = FALSE;
g.icedpools = FALSE;
/* achievement tracking; static init would suffice except we need to
reset if #wizmakemap is used to recreate mines' end or sokoban end;
once either level is created, these values can be forgotten */
g.mines_prize_count = g.soko_prize_count = 0;
for (tmpi = 0; tmpi <= MAX_NESTED_ROOMS; tmpi++) {
coder->tmproomlist[tmpi] = (struct mkroom *) 0;