diff --git a/dat/dungeon.lua b/dat/dungeon.lua index 6112a25ad..014d3dab3 100644 --- a/dat/dungeon.lua +++ b/dat/dungeon.lua @@ -178,7 +178,8 @@ dungeon = { }, { name = "minend", - bonetag = "E", +-- 3.7.0: minend changed to no-bones to simplify achievement tracking +-- bonetag = "E" base = -1, nlevels = 3 }, diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 539ce7f9a..dfd769792 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 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 diff --git a/include/context.h b/include/context.h index 4e8b01f62..dafc0c3f8 100644 --- a/include/context.h +++ b/include/context.h @@ -105,6 +105,11 @@ struct novel_tracking { /* for choosing random passage when reading novel */ 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) */ @@ -143,6 +148,7 @@ struct context_info { 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 */ diff --git a/include/decl.h b/include/decl.h index d6100e241..54a58aa03 100644 --- a/include/decl.h +++ b/include/decl.h @@ -215,7 +215,7 @@ typedef struct { 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 */ @@ -1184,15 +1184,13 @@ struct instance_globals { 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; diff --git a/include/obj.h b/include/obj.h index b31181394..6b0d9c5e9 100644 --- a/include/obj.h +++ b/include/obj.h @@ -346,19 +346,9 @@ struct obj { && !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 diff --git a/include/patchlevel.h b/include/patchlevel.h index 50eccc543..f9196a31f 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -14,7 +14,7 @@ * 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 \ diff --git a/src/bones.c b/src/bones.c index 8ebe47cea..6816baa87 100644 --- a/src/bones.c +++ b/src/bones.c @@ -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; diff --git a/src/cmd.c b/src/cmd.c index 90dd2be5c..46f210f19 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -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 diff --git a/src/decl.c b/src/decl.c index 8ae9aa6c7..79cbd199a 100644 --- a/src/decl.c +++ b/src/decl.c @@ -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 */ diff --git a/src/invent.c b/src/invent.c index 6c3d7c363..88d6fb1d3 100644 --- a/src/invent.c +++ b/src/invent.c @@ -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; } } diff --git a/src/options.c b/src/options.c index 0fc52a0af..f6f572689 100644 --- a/src/options.c +++ b/src/options.c @@ -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, diff --git a/src/sp_lev.c b/src/sp_lev.c index c463c8b74..3a76dbb72 100644 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -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;