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:
@@ -178,7 +178,8 @@ dungeon = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "minend",
|
name = "minend",
|
||||||
bonetag = "E",
|
-- 3.7.0: minend changed to no-bones to simplify achievement tracking
|
||||||
|
-- bonetag = "E"
|
||||||
base = -1,
|
base = -1,
|
||||||
nlevels = 3
|
nlevels = 3
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ when poly'd into a giant and moving onto a boulder's spot, message given was
|
|||||||
random role selection wasn't honoring unwanted alignment(s) properly
|
random role selection wasn't honoring unwanted alignment(s) properly
|
||||||
if at the edge of the map window, trying to move farther fails but used a turn
|
if at the edge of the map window, trying to move farther fails but used a turn
|
||||||
hero can no longer wear blindfold/towel/lenses when poly'd into headless form
|
hero can no longer wear blindfold/towel/lenses when poly'd into headless form
|
||||||
|
revamp achievement tracking for exploring Mine's End and Sokoban (by acquiring
|
||||||
|
luckstone and bag of holding or amulet of reflection, respectively)
|
||||||
|
|
||||||
|
|
||||||
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
|
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ struct novel_tracking { /* for choosing random passage when reading novel */
|
|||||||
passage from the Death Quotes section of dat/tribute */
|
passage from the Death Quotes section of dat/tribute */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct achievement_tracking {
|
||||||
|
unsigned mines_prize_oid, soko_prize_oid; /* obj->o_id */
|
||||||
|
short mines_prize_type, soko_prize_typ1, soko_prize_typ2; /* obj->otyp */
|
||||||
|
};
|
||||||
|
|
||||||
struct context_info {
|
struct context_info {
|
||||||
unsigned ident; /* social security number for each monster */
|
unsigned ident; /* social security number for each monster */
|
||||||
unsigned no_of_wizards; /* 0, 1 or 2 (wizard and his shadow) */
|
unsigned no_of_wizards; /* 0, 1 or 2 (wizard and his shadow) */
|
||||||
@@ -143,6 +148,7 @@ struct context_info {
|
|||||||
struct obj_split objsplit; /* track most recently split object stack */
|
struct obj_split objsplit; /* track most recently split object stack */
|
||||||
struct tribute_info tribute;
|
struct tribute_info tribute;
|
||||||
struct novel_tracking novel;
|
struct novel_tracking novel;
|
||||||
|
struct achievement_tracking achieveo;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* CONTEXT_H */
|
#endif /* CONTEXT_H */
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ typedef struct {
|
|||||||
boolean fieldlevel; /* fieldlevel saves saves each field individually */
|
boolean fieldlevel; /* fieldlevel saves saves each field individually */
|
||||||
boolean addinfo; /* if set, some additional context info from core */
|
boolean addinfo; /* if set, some additional context info from core */
|
||||||
boolean eof; /* place to mark eof reached */
|
boolean eof; /* place to mark eof reached */
|
||||||
boolean bendian; /* set to true if executing on a big-endian machine */
|
boolean bendian; /* set to true if executing on big-endian machine */
|
||||||
FILE *fpdef; /* file pointer for fieldlevel default style */
|
FILE *fpdef; /* file pointer for fieldlevel default style */
|
||||||
FILE *fpdefmap; /* file pointer mapfile for def format */
|
FILE *fpdefmap; /* file pointer mapfile for def format */
|
||||||
FILE *fplog; /* file pointer logfile */
|
FILE *fplog; /* file pointer logfile */
|
||||||
@@ -1184,15 +1184,13 @@ struct instance_globals {
|
|||||||
char *lev_message;
|
char *lev_message;
|
||||||
lev_region *lregions;
|
lev_region *lregions;
|
||||||
int num_lregions;
|
int num_lregions;
|
||||||
/* positions touched by level elements explicitly defined in the des-file */
|
/* positions touched by level elements explicitly defined in des-file */
|
||||||
char SpLev_Map[COLNO][ROWNO];
|
char SpLev_Map[COLNO][ROWNO];
|
||||||
struct sp_coder *coder;
|
struct sp_coder *coder;
|
||||||
xchar xstart, ystart;
|
xchar xstart, ystart;
|
||||||
char xsize, ysize;
|
char xsize, ysize;
|
||||||
boolean splev_init_present;
|
boolean splev_init_present;
|
||||||
boolean icedpools;
|
boolean icedpools;
|
||||||
int mines_prize_count;
|
|
||||||
int soko_prize_count; /* achievements */
|
|
||||||
struct obj *container_obj[MAX_CONTAINMENT];
|
struct obj *container_obj[MAX_CONTAINMENT];
|
||||||
int container_idx;
|
int container_idx;
|
||||||
struct monst *invent_carrying_monster;
|
struct monst *invent_carrying_monster;
|
||||||
|
|||||||
@@ -346,19 +346,9 @@ struct obj {
|
|||||||
&& !undiscovered_artifact(ART_EYES_OF_THE_OVERWORLD)))
|
&& !undiscovered_artifact(ART_EYES_OF_THE_OVERWORLD)))
|
||||||
#define pair_of(o) ((o)->otyp == LENSES || is_gloves(o) || is_boots(o))
|
#define pair_of(o) ((o)->otyp == LENSES || is_gloves(o) || is_boots(o))
|
||||||
|
|
||||||
/* 'PRIZE' values override obj->corpsenm so prizes mustn't be object types
|
/* achievement tracking; 3.6.x did this differently */
|
||||||
which use that field for monster type (or other overloaded purpose) */
|
#define is_mines_prize(o) ((o)->o_id == g.context.achieveo.mines_prize_oid)
|
||||||
#define MINES_PRIZE 1
|
#define is_soko_prize(o) ((o)->o_id == g.context.achieveo.soko_prize_oid)
|
||||||
#define SOKO_PRIZE1 2
|
|
||||||
#define SOKO_PRIZE2 3
|
|
||||||
#define is_mines_prize(o) \
|
|
||||||
((o)->otyp == iflags.mines_prize_type \
|
|
||||||
&& (o)->record_achieve_special == MINES_PRIZE)
|
|
||||||
#define is_soko_prize(o) \
|
|
||||||
(((o)->otyp == iflags.soko_prize_type1 \
|
|
||||||
&& (o)->record_achieve_special == SOKO_PRIZE1) \
|
|
||||||
|| ((o)->otyp == iflags.soko_prize_type2 \
|
|
||||||
&& (o)->record_achieve_special == SOKO_PRIZE2))
|
|
||||||
|
|
||||||
/* Flags for get_obj_location(). */
|
/* Flags for get_obj_location(). */
|
||||||
#define CONTAINED_TOO 0x1
|
#define CONTAINED_TOO 0x1
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* Incrementing EDITLEVEL can be used to force invalidation of old bones
|
* Incrementing EDITLEVEL can be used to force invalidation of old bones
|
||||||
* and save files.
|
* and save files.
|
||||||
*/
|
*/
|
||||||
#define EDITLEVEL 10
|
#define EDITLEVEL 11
|
||||||
|
|
||||||
#define COPYRIGHT_BANNER_A "NetHack, Copyright 1985-2020"
|
#define COPYRIGHT_BANNER_A "NetHack, Copyright 1985-2020"
|
||||||
#define COPYRIGHT_BANNER_B \
|
#define COPYRIGHT_BANNER_B \
|
||||||
|
|||||||
12
src/bones.c
12
src/bones.c
@@ -165,14 +165,10 @@ boolean restore;
|
|||||||
if (mnum == PM_DOPPELGANGER && otmp->otyp == CORPSE)
|
if (mnum == PM_DOPPELGANGER && otmp->otyp == CORPSE)
|
||||||
set_corpsenm(otmp, mnum);
|
set_corpsenm(otmp, mnum);
|
||||||
}
|
}
|
||||||
} else if ((otmp->otyp == iflags.mines_prize_type
|
} else if (is_mines_prize(otmp) || is_soko_prize(otmp)) {
|
||||||
&& !Is_mineend_level(&u.uz))
|
/* achievement tracking; in case prize was moved off its
|
||||||
|| ((otmp->otyp == iflags.soko_prize_type1
|
original level (which is always a no-bones level) */
|
||||||
|| otmp->otyp == iflags.soko_prize_type2)
|
otmp->nomerge = 0;
|
||||||
&& !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 (otmp->otyp == AMULET_OF_YENDOR) {
|
} else if (otmp->otyp == AMULET_OF_YENDOR) {
|
||||||
/* no longer the real Amulet */
|
/* no longer the real Amulet */
|
||||||
otmp->otyp = FAKE_AMULET_OF_YENDOR;
|
otmp->otyp = FAKE_AMULET_OF_YENDOR;
|
||||||
|
|||||||
19
src/cmd.c
19
src/cmd.c
@@ -806,6 +806,24 @@ boolean pre, wiztower;
|
|||||||
continue;
|
continue;
|
||||||
if (mtmp->isshk)
|
if (mtmp->isshk)
|
||||||
setpaid(mtmp);
|
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?
|
/* TODO?
|
||||||
* Reduce 'born' tally for each monster about to be discarded
|
* Reduce 'born' tally for each monster about to be discarded
|
||||||
* by savelev(), otherwise replacing heavily populated levels
|
* by savelev(), otherwise replacing heavily populated levels
|
||||||
@@ -877,6 +895,7 @@ wiz_makemap(VOID_ARGS)
|
|||||||
{
|
{
|
||||||
if (wizard) {
|
if (wizard) {
|
||||||
boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz);
|
boolean was_in_W_tower = In_W_tower(u.ux, u.uy, &u.uz);
|
||||||
|
|
||||||
makemap_prepost(TRUE, was_in_W_tower);
|
makemap_prepost(TRUE, was_in_W_tower);
|
||||||
/* create a new level; various things like bestowing a guardian
|
/* create a new level; various things like bestowing a guardian
|
||||||
angel on Astral or setting off alarm on Ft.Ludios are handled
|
angel on Astral or setting off alarm on Ft.Ludios are handled
|
||||||
|
|||||||
@@ -638,8 +638,6 @@ const struct instance_globals g_init = {
|
|||||||
UNDEFINED_VALUE, /* ysize */
|
UNDEFINED_VALUE, /* ysize */
|
||||||
FALSE, /* splev_init_present */
|
FALSE, /* splev_init_present */
|
||||||
FALSE, /* icedpools */
|
FALSE, /* icedpools */
|
||||||
0, /* mines_prize_count */
|
|
||||||
0, /* soki_prize_count */
|
|
||||||
{ UNDEFINED_PTR }, /* container_obj */
|
{ UNDEFINED_PTR }, /* container_obj */
|
||||||
0, /* container_idx */
|
0, /* container_idx */
|
||||||
NULL, /* invent_carrying_monster */
|
NULL, /* invent_carrying_monster */
|
||||||
|
|||||||
12
src/invent.c
12
src/invent.c
@@ -835,20 +835,14 @@ struct obj *obj;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* "special achievements" aren't discoverable during play, they
|
/* "special achievements" aren't discoverable during play, they
|
||||||
end up being recorded in XLOGFILE at end of game, nowhere else;
|
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) */
|
|
||||||
if (is_mines_prize(obj)) {
|
if (is_mines_prize(obj)) {
|
||||||
u.uachieve.mines_luckstone = 1;
|
u.uachieve.mines_luckstone = 1;
|
||||||
obj->record_achieve_special = NON_PM;
|
g.context.achieveo.mines_prize_oid = 0;
|
||||||
obj->nomerge = 0;
|
obj->nomerge = 0;
|
||||||
} else if (is_soko_prize(obj)) {
|
} else if (is_soko_prize(obj)) {
|
||||||
u.uachieve.finish_sokoban = 1;
|
u.uachieve.finish_sokoban = 1;
|
||||||
obj->record_achieve_special = NON_PM;
|
g.context.achieveo.soko_prize_oid = 0;
|
||||||
obj->nomerge = 0;
|
obj->nomerge = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -737,9 +737,9 @@ initoptions_init()
|
|||||||
|
|
||||||
/* for "special achievement" tracking (see obj.h,
|
/* for "special achievement" tracking (see obj.h,
|
||||||
create_object(sp_lev.c), addinv_core1(invent.c) */
|
create_object(sp_lev.c), addinv_core1(invent.c) */
|
||||||
iflags.mines_prize_type = LUCKSTONE;
|
g.context.achieveo.mines_prize_type = LUCKSTONE;
|
||||||
iflags.soko_prize_type1 = BAG_OF_HOLDING;
|
g.context.achieveo.soko_prize_typ1 = BAG_OF_HOLDING;
|
||||||
iflags.soko_prize_type2 = AMULET_OF_REFLECTION;
|
g.context.achieveo.soko_prize_typ2 = AMULET_OF_REFLECTION;
|
||||||
|
|
||||||
/* assert( sizeof flags.inv_order == sizeof def_inv_order ); */
|
/* assert( sizeof flags.inv_order == sizeof def_inv_order ); */
|
||||||
(void) memcpy((genericptr_t) flags.inv_order,
|
(void) memcpy((genericptr_t) flags.inv_order,
|
||||||
|
|||||||
70
src/sp_lev.c
70
src/sp_lev.c
@@ -1569,32 +1569,34 @@ struct mkroom *croom;
|
|||||||
if (o->id != -1) {
|
if (o->id != -1) {
|
||||||
static const char prize_warning[] = "multiple prizes on %s level";
|
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
|
* If this is a specific item of the right type and it is being
|
||||||
used to detect a special achievement (to whit, reaching and
|
* created on the right level, flag it as the designated item
|
||||||
exploring the target level, although the exploration part
|
* used to detect a special achievement (to whit, reaching and
|
||||||
might be short-circuited if a monster brings object to hero) */
|
* exploring the target level, although the exploration part
|
||||||
if (Is_mineend_level(&u.uz)) {
|
* might be short-circuited if a monster brings object to hero).
|
||||||
if (otmp->otyp == iflags.mines_prize_type) {
|
*
|
||||||
if (!g.mines_prize_count++) {
|
* Random items of the appropriate type won't trigger a false
|
||||||
/* Note: the first luckstone on lev will become the prize
|
* match--they'll fail the (id != -1) test above--but the level
|
||||||
even if its not the explicit one, but random */
|
* definition should not include a second instance of any prize.
|
||||||
otmp->record_achieve_special = MINES_PRIZE;
|
*/
|
||||||
/* prevent stacking; cleared when achievement is recorded */
|
if (Is_mineend_level(&u.uz)
|
||||||
otmp->nomerge = 1;
|
&& 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)) {
|
} else if (Is_sokoend_level(&u.uz)
|
||||||
if (otmp->otyp == iflags.soko_prize_type1) {
|
&& (otmp->otyp == g.context.achieveo.soko_prize_typ1
|
||||||
otmp->record_achieve_special = SOKO_PRIZE1;
|
|| 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 */
|
otmp->nomerge = 1; /* redundant; Sokoban prizes don't stack */
|
||||||
if (++g.soko_prize_count > 1)
|
} else {
|
||||||
impossible(prize_warning, "sokoban end");
|
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2590,14 +2592,26 @@ const char *s;
|
|||||||
|
|
||||||
/* find by object name */
|
/* find by object name */
|
||||||
for (i = 0; i < NUM_OBJECTS; i++) {
|
for (i = 0; i < NUM_OBJECTS; i++) {
|
||||||
objname = obj_descr[i].oc_name;
|
objname = OBJ_NAME(objects[i]);
|
||||||
if (objname && !strcmpi(s, objname))
|
if (objname && !strcmpi(s, objname))
|
||||||
return i;
|
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 */
|
/* find by object description */
|
||||||
for (i = 0; i < NUM_OBJECTS; i++) {
|
for (i = 0; i < NUM_OBJECTS; i++) {
|
||||||
objname = obj_descr[i].oc_descr;
|
objname = OBJ_DESCR(objects[i]);
|
||||||
if (objname && !strcmpi(s, objname))
|
if (objname && !strcmpi(s, objname))
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -5419,10 +5433,6 @@ sp_level_coder_init()
|
|||||||
|
|
||||||
g.splev_init_present = FALSE;
|
g.splev_init_present = FALSE;
|
||||||
g.icedpools = 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++) {
|
for (tmpi = 0; tmpi <= MAX_NESTED_ROOMS; tmpi++) {
|
||||||
coder->tmproomlist[tmpi] = (struct mkroom *) 0;
|
coder->tmproomlist[tmpi] = (struct mkroom *) 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user