]> granicus.if.org Git - nethack/commitdiff
simplify achievement tracking for special objects
authorPatR <rankin@nethack.org>
Fri, 24 Jan 2020 21:54:23 +0000 (13:54 -0800)
committerPatR <rankin@nethack.org>
Fri, 24 Jan 2020 21:54:23 +0000 (13:54 -0800)
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.

12 files changed:
dat/dungeon.lua
doc/fixes37.0
include/context.h
include/decl.h
include/obj.h
include/patchlevel.h
src/bones.c
src/cmd.c
src/decl.c
src/invent.c
src/options.c
src/sp_lev.c

index 6112a25ad9f82499335e28a97349195d37c2a011..014d3dab32fc0626420a680d07383493aecaa6e2 100644 (file)
@@ -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
          },
index 539ce7f9abfb25033a2c66853a2e0814f0405130..dfd7697929bc2f8149c8f7e7e628229595bc8b9b 100644 (file)
@@ -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
index 4e8b01f62a73fb6bd6f3d84778c265fdcd93bd16..dafc0c3f8f05b2089b545129b31a568c1490e111 100644 (file)
@@ -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 */
index d6100e2414fd6ca54c1444004f75830afb107355..54a58aa03222ad4bae76f3bb43d567c9e520aba4 100644 (file)
@@ -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 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;
index b31181394fa9fab94be2d83a89d548cf010c5be8..6b0d9c5e90600116ff0eadd52cd279f0f243396b 100644 (file)
@@ -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
index 50eccc543d2e5e44b24ffb5795aeb5c41fa983c4..f9196a31fc7c7a9b1b836b16ec424c941fffe31b 100644 (file)
@@ -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 \
index 8ebe47ceaa415cec3e2bedff02d0c70137f1768c..6816baa876e732a050679f5de36a61d0c35dfc44 100644 (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;
index 90dd2be5cc0ad1ade2e28cbc1de4b2a2de1f2d65..46f210f1964547ef1e9ca0b79c9544d2aa47eb24 100644 (file)
--- 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
index 8ae9aa6c7543126a41cabe283b2b8f2849e9cec0..79cbd199aff4f85ad72c99ca6f235e17c4c36733 100644 (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 */
index 6c3d7c36356295f8a2c7a9bd52f561368e9afe11..88d6fb1d323ef00deceed41d9bfe1e372961d781 100644 (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;
     }
 }
index 0fc52a0afbbea5e74d2fd14da129fbe3aa718e8d..f6f572689b1f35a15a4ee5f76e2a6112bd917613 100644 (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,
index c463c8b74c7826997296504b3265109cee9bd7b3..3a76dbb72605b3b4850081137220f5f86c46cc2e 100644 (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;
-                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");
             }
         }
     }
@@ -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;