},
{
name = "minend",
- bonetag = "E",
+-- 3.7.0: minend changed to no-bones to simplify achievement tracking
+-- bonetag = "E"
base = -1,
nlevels = 3
},
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
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
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 {
unsigned ident; /* social security number for each monster */
unsigned no_of_wizards; /* 0, 1 or 2 (wizard and his shadow) */
struct obj_split objsplit; /* track most recently split object stack */
struct tribute_info tribute;
struct novel_tracking novel;
+ struct achievement_tracking achieveo;
};
#endif /* CONTEXT_H */
boolean fieldlevel; /* fieldlevel saves saves each field individually */
boolean addinfo; /* if set, some additional context info from core */
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 *fpdefmap; /* file pointer mapfile for def format */
FILE *fplog; /* file pointer logfile */
char *lev_message;
lev_region *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];
struct sp_coder *coder;
xchar xstart, ystart;
char xsize, ysize;
boolean splev_init_present;
boolean icedpools;
- int mines_prize_count;
- int soko_prize_count; /* achievements */
struct obj *container_obj[MAX_CONTAINMENT];
int container_idx;
struct monst *invent_carrying_monster;
&& !undiscovered_artifact(ART_EYES_OF_THE_OVERWORLD)))
#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
- which use that field for monster type (or other overloaded purpose) */
-#define MINES_PRIZE 1
-#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))
+/* achievement tracking; 3.6.x did this differently */
+#define is_mines_prize(o) ((o)->o_id == g.context.achieveo.mines_prize_oid)
+#define is_soko_prize(o) ((o)->o_id == g.context.achieveo.soko_prize_oid)
/* Flags for get_obj_location(). */
#define CONTAINED_TOO 0x1
* Incrementing EDITLEVEL can be used to force invalidation of old bones
* and save files.
*/
-#define EDITLEVEL 10
+#define EDITLEVEL 11
#define COPYRIGHT_BANNER_A "NetHack, Copyright 1985-2020"
#define COPYRIGHT_BANNER_B \
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;
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
{
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
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 */
}
/* "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;
}
}
/* 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,
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;
- 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;
+ } 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 {
+ impossible(prize_warning, "sokoban end");
}
}
}
/* 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;
}
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;