]> granicus.if.org Git - nethack/commitdiff
bubble/cloud save/restore overhaul
authorPatR <rankin@nethack.org>
Fri, 29 Apr 2022 19:44:26 +0000 (12:44 -0700)
committerPatR <rankin@nethack.org>
Fri, 29 Apr 2022 19:44:26 +0000 (12:44 -0700)
The air bubbles on the Plane of Water and the clouds on the Plane of
Air were being saved and restored as part of the current level's state
(which is the 'u' struct and invent and such) rather than with the
current level itself.  That was ok for normal play, but for wizard
mode's ^V allowing you to return to a previously visited endgame level
after moving to a different one it meant a new set of bubbles for
Water and new set of clouds for Air.  Even that was ok since it only
applied to wizard mode, but using #wizmakemap to recreate Water or Air
while you were on it added a new set of bubbles or clouds to the
existing ones.  If repeated, eventually there wouldn't be much water
or air left.

Instead of just adding a hack to #wizmakemap, change save/restore to
keep the bubbles/clouds with the level rather than with the state.
That wasn't trivial and now I know why the old odd arrangement was
chosen.  Saving hides u.uz by zeroing it out for levels that the hero
isn't on and it is zero during restore so simple checks for whether a
given level is water or air won't work.

This also adds another non-file/non-debugpline() use of DEBUGFILES:
 DEBUGFILES=seethru nethack -D
will make water and clouds be transparent instead of opaque.  It also
makes fumaroles and other light-blocking gas clouds be transparent
which wasn't really intended, but avoiding it would be extra work that
doesn't accomplish much.

Increments EDITLEVEL for the third time this week....

doc/fixes3-7-0.txt
include/decl.h
include/patchlevel.h
src/decl.c
src/do.c
src/mkmaze.c
src/restore.c
src/save.c
src/sfstruct.c
src/vision.c

index 64e9bbcb9464e0c50041b53fe0404c77dbd76572..19e95bd3ad73e6ca053526306bc011cbe3361e63 100644 (file)
@@ -898,6 +898,9 @@ trap detection could falsely find trapped secret doors; those can't be trapped
        due to details of how they use overlaid fields in the rm structure
 for force-fight against edge of level, report "you harmlessly attack unknown
        obstacle" rather than "you have moved as far <direction> as possible"
+using wizard mode ^V in endgame to return to previously visited Plane of Water
+       now gets the same air bubbles back instead of a replacement set;
+       likewise for clouds on Plane of Air
 
 
 Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
@@ -1185,6 +1188,9 @@ the change to protect Rider corpses from being destroyed by exploding chests
        inadvertently prevented them from being used up when Riders revived
 force-fight against furniture covered by an object described the attack as
        missing an unknown obstacle
+using #wizmakemap on Plane of Water added a new set of air bubbles each time
+       it was run and eventually replaced just about all the water; likewise
+       with clouds on Plane of Air
 
 curses: 'msg_window' option wasn't functional for curses unless the binary
        also included tty support
@@ -1603,6 +1609,9 @@ pets are more likely to follow you closely if you are carrying something they
 allow setting msgtype in SOUND line
 display detected door traps and chest traps as trapped doors and trapped
        chests rather than as fake bear traps
+if built with DEBUG enabled and running in wizard mode, starting play with
+       DEBUGFILES=seethru in environment makes clouds on the Plane of Air,
+       water on Plane of Water, and fumaroles on Plane of Fire be transparent
 
 
 Platform- and/or Interface-Specific New Features
@@ -1822,7 +1831,7 @@ pm.h dependency and inclusion is removed and comparable functionality is
        file called monsters.h, then taking advantage of the C preprocessor to
        generate appropriate enum values during compile
 onames.h dependency and inclusion is removed and comparable functionality is
-       produced by moving the object definitions from objects.c into new header
+       produced by moving object definitions from objects.c into new header
        file called objects.h, then taking advantage of the C preprocessor to
        generate appropriate enum values during compile
 artilist.h is used to generate appropriate artifact enum values by the C
@@ -1867,4 +1876,6 @@ remove obsolete roles[].femalenum and rename roles[].malenum to roles[].mnum;
 add git submodule support to the Makefiles by specifying git=1 or GIT=1 on the
        make command
 add TT_NONE==0, renumber other u.utraptype values so that TT_BEARTRAP isn't 0
+for Planes of Water and Air, save the air bubbles and clouds with the level
+       rather than as game state; affects wizard mode ^V and #wizmakemap
 
index 745a00ff0ad9ea2891b71d51b3f63c4a65899dd8..04e60f00efc0a97c87425cba3424982e9b1443d0 100644 (file)
@@ -1165,6 +1165,7 @@ struct instance_globals {
     unsigned usteed_id; /* need to preserve during save */
     struct obj *looseball;  /* track uball during save and... */
     struct obj *loosechain; /* track uchain since saving might free it */
+    d_level uz_save;
 
     /* shk.c */
     /* auto-response flag for/from "sell foo?" 'a' => 'y', 'q' => 'n' */
@@ -1231,6 +1232,7 @@ struct instance_globals {
     xchar *viz_rmin;                   /* min could see indices */
     xchar *viz_rmax;                   /* max could see indices */
     boolean vision_full_recalc;
+    int seethru; /* 'bubble' debugging: clouds and water don't block light */
 
     /* weapon.c */
     struct obj *propellor;
index 28738c0d8326f0d56ec2d9131e40baa98f3915ca..e3e623000c3a5eb085e3b2fffb02f180468ea08a 100644 (file)
@@ -17,7 +17,7 @@
  * Incrementing EDITLEVEL can be used to force invalidation of old bones
  * and save files.
  */
-#define EDITLEVEL 55
+#define EDITLEVEL 56
 
 /*
  * Development status possibilities.
index 8164f3cbbf71d41f816382940826971913b72728..805df492d6ab712df1fd52ab8a1951b16a2213b1 100644 (file)
@@ -615,6 +615,7 @@ const struct instance_globals g_init = {
     0, /* usteed_id */
     (struct obj *) 0, /* looseball */
     (struct obj *) 0, /* loosechain */
+    { 0, 0 }, /* uz_save */
 
     /* shk.c */
     'a', /* sell_response */
@@ -670,6 +671,7 @@ const struct instance_globals g_init = {
     NULL, /* viz_rmin */
     NULL, /* viz_rmax */
     FALSE, /* vision_full_recalc */
+    0, /* seethru */
 
     /* weapon.c */
     UNDEFINED_PTR, /* propellor */
index d371dc26f5e53bbddf9d60d0d8a5bdf5a011b164..ab39c438fad8a668769876da9a834affd058855b 100644 (file)
--- a/src/do.c
+++ b/src/do.c
@@ -1444,22 +1444,6 @@ goto_level(
     nhfp->mode = cant_go_back ? FREEING : (WRITING | FREEING);
     savelev(nhfp, ledger_no(&u.uz));
     nhfp->mode = save_mode;
-    /* air bubbles and clouds are saved in game-state rather than with the
-       level they're used on; in normal play, you can't leave and return
-       to any endgame level--bubbles aren't needed once you move to the
-       next level so used to be discarded when the next special level was
-       loaded; but in wizard mode you can leave and return, and since they
-       aren't saved with the level and restored upon return (new ones are
-       created instead), we need to discard them to avoid a memory leak;
-       so bubbles are now discarded as we leave the level they're used on */
-    if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) {
-        NHFILE tmpnhfp;
-
-        zero_nhfile(&tmpnhfp);
-        tmpnhfp.fd = -1;
-        tmpnhfp.mode = FREEING;
-        save_waterlevel(&tmpnhfp);
-    }
     close_nhfile(nhfp);
     if (cant_go_back) {
         /* discard unreachable levels; keep #0 */
@@ -1517,16 +1501,6 @@ goto_level(
         reseed_random(rn2_on_display_rng);
         minit(); /* ZEROCOMP */
         getlev(nhfp, g.hackpid, new_ledger);
-        /* when in wizard mode, it is possible to leave from and return to
-           any level in the endgame; above, we discarded bubble/cloud info
-           when leaving Plane of Water or Air so recreate some now */
-        if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) {
-            NHFILE tmpnhfp;
-
-            zero_nhfile(&tmpnhfp);
-            tmpnhfp.fd = -1;
-            restore_waterlevel(&tmpnhfp);
-        }
         close_nhfile(nhfp);
         oinit(); /* reassign level dependent obj probabilities */
     }
index 05620488acee0bb1beb9abcef27791ca93de96ac..bec803d3a32620aa28abd5421f1f642bc03d5b2b 100644 (file)
@@ -1592,7 +1592,7 @@ save_waterlevel(NHFILE* nhfp)
 {
     struct bubble *b;
 
-    if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz))
+    if (!g.bbubbles)
         return;
 
     if (perform_bwrite(nhfp)) {
@@ -1621,17 +1621,6 @@ restore_waterlevel(NHFILE* nhfp)
     struct bubble *b = (struct bubble *) 0, *btmp;
     int i, n = 0;
 
-    if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz))
-        return;
-
-    if (nhfp->fd == -1) { /* special handling for restore in goto_level() */
-        if (!wizard)
-            impossible("restore_waterlevel: returning to %s?",
-                       Is_waterlevel(&u.uz) ? "Water" : "Air");
-        setup_waterlevel();
-        return;
-    }
-
     set_wportal();
     if (nhfp->structlevel) {
         mread(nhfp->fd,(genericptr_t)&n,sizeof(int));
@@ -1655,7 +1644,21 @@ restore_waterlevel(NHFILE* nhfp)
       mv_bubble(b, 0, 0, TRUE);
     }
     g.ebubbles = b;
-    b->next = (struct bubble *) 0;
+    if (b) {
+        b->next = (struct bubble *) 0;
+    } else {
+        /* avoid "saving and reloading may fix this" */
+        g.program_state.something_worth_saving = 0;
+        /* during restore, information about what level this is might not
+           be available so we're wishy-washy about what we describe */
+        impossible("No %s to restore?",
+                   (Is_waterlevel(&u.uz) || Is_waterlevel(&g.uz_save))
+                   ? "air bubbles"
+                   : (Is_airlevel(&u.uz) || Is_airlevel(&g.uz_save))
+                     ? "clouds"
+                     : "air bubbles or clouds");
+        g.program_state.something_worth_saving = 1;
+    }
 }
 
 static void
index 5ded5580dcd76a14abd545f14bd0dade0ba10715..c3e208bb09c8a1a4ca653c1cb8f7e789772ab3e3 100644 (file)
@@ -30,6 +30,7 @@ static void ghostfruit(struct obj *);
 static boolean restgamestate(NHFILE *, unsigned int *, unsigned int *);
 static void restlevelstate(unsigned int, unsigned int);
 static int restlevelfile(xchar);
+static void rest_bubbles(NHFILE *);
 static void restore_gamelog(NHFILE *);
 static void restore_msghistory(NHFILE *);
 static void reset_oattached_mids(boolean);
@@ -683,7 +684,6 @@ restgamestate(NHFILE* nhfp, unsigned int* stuckid, unsigned int* steedid)
     g.ffruit = loadfruitchn(nhfp);
 
     restnames(nhfp);
-    restore_waterlevel(nhfp);
     restore_msghistory(nhfp);
     restore_gamelog(nhfp);
     /* must come after all mons & objs are restored */
@@ -1127,6 +1127,7 @@ getlev(NHFILE* nhfp, int pid, xchar lev)
     }
     restdamage(nhfp);
     rest_regions(nhfp);
+    rest_bubbles(nhfp); /* for water and air; empty marker on other levels */
 
     if (ghostly) {
         stairway *stway = g.stairs;
@@ -1217,6 +1218,23 @@ get_plname_from_file(NHFILE* nhfp, char *plbuf)
     return;
 }
 
+/* restore Plane of Water's air bubbles and Plane of Air's clouds */
+static void
+rest_bubbles(NHFILE *nhfp)
+{
+    xchar bbubbly;
+
+    /* whether or not the Plane of Water's air bubbles or Plane of Air's
+       clouds are present is recorded during save so that we don't have to
+       know what level is being restored */
+    bbubbly = 0;
+    if (nhfp->structlevel)
+        mread(nhfp->fd, (genericptr_t) &bbubbly, sizeof bbubbly);
+
+    if (bbubbly)
+        restore_waterlevel(nhfp);
+}
+
 static void
 restore_gamelog(NHFILE* nhfp)
 {
index bbca8cf9fd6760a25ba851f73593756ab264101a..89516275a7b74607835eb7f0e6437521d81ac0f9 100644 (file)
@@ -19,6 +19,7 @@ int dotcnt, dotrow; /* also used in restore */
 static void savelevchn(NHFILE *);
 static void savelevl(NHFILE *,boolean);
 static void savedamage(NHFILE *);
+static void save_bubbles(NHFILE *, xchar);
 static void save_stairs(NHFILE *);
 static void saveobj(NHFILE *,struct obj *);
 static void saveobjchn(NHFILE *,struct obj **);
@@ -27,6 +28,7 @@ static void savemonchn(NHFILE *,struct monst *);
 static void savetrapchn(NHFILE *,struct trap *);
 static void save_gamelog(NHFILE *);
 static void savegamestate(NHFILE *);
+static void savelev_core(NHFILE *, xchar);
 static void save_msghistory(NHFILE *);
 
 #ifdef ZEROCOMP
@@ -76,7 +78,6 @@ dosave0(void)
 {
     const char *fq_save;
     xchar ltmp;
-    d_level uz_save;
     char whynot[BUFSZ];
     NHFILE *nhfp, *onhfp;
     int res = 0;
@@ -174,8 +175,9 @@ dosave0(void)
      * parts of the restore code from completely initializing all
      * in-core data structures, since all we're doing is copying.
      * This also avoids at least one nasty core dump.
+     * [g.uz_save is used by save_bubbles() as well as to restore u.uz]
      */
-    uz_save = u.uz;
+    g.uz_save = u.uz;
     u.uz.dnum = u.uz.dlevel = 0;
     /* these pointers are no longer valid, and at least u.usteed
      * may mislead place_monster() on other levels
@@ -184,7 +186,7 @@ dosave0(void)
     u.usteed = (struct monst *) 0;
 
     for (ltmp = (xchar) 1; ltmp <= maxledgerno(); ltmp++) {
-        if (ltmp == ledger_no(&uz_save))
+        if (ltmp == ledger_no(&g.uz_save))
             continue;
         if (!(g.level_info[ltmp].flags & LFILE_EXISTS))
             continue;
@@ -218,7 +220,8 @@ dosave0(void)
     }
     close_nhfile(nhfp);
 
-    u.uz = uz_save;
+    u.uz = g.uz_save;
+    g.uz_save.dnum = g.uz_save.dlevel = 0;
 
     /* get rid of current level --jgm */
     delete_levelfile(ledger_no(&u.uz));
@@ -349,7 +352,6 @@ savegamestate(NHFILE* nhfp)
     }
     savefruitchn(nhfp);
     savenames(nhfp);
-    save_waterlevel(nhfp);
     save_msghistory(nhfp);
     save_gamelog(nhfp);
     if (nhfp->structlevel)
@@ -457,6 +459,27 @@ savestateinlock(void)
 
 void
 savelev(NHFILE *nhfp, xchar lev)
+{
+    boolean set_uz_save = (g.uz_save.dnum == 0 && g.uz_save.dlevel == 0);
+
+    /* caller might have already set up g.uz_save and zeroed u.uz;
+       if not, we need to set it for save_bubbles() */
+    if (set_uz_save) {
+        if (u.uz.dnum == 0 && u.uz.dlevel == 0) {
+            g.program_state.something_worth_saving = 0;
+            panic("savelev: where are we?");
+        }
+        g.uz_save = u.uz;
+    }
+
+    savelev_core(nhfp, lev);
+
+    if (set_uz_save)
+        g.uz_save.dnum = g.uz_save.dlevel = 0; /* unset */
+}
+
+static void
+savelev_core(NHFILE *nhfp, xchar lev)
 {
 #ifdef TOS
     short tlev;
@@ -544,6 +567,8 @@ savelev(NHFILE *nhfp, xchar lev)
     save_engravings(nhfp);
     savedamage(nhfp); /* pending shop wall and/or floor repair */
     save_regions(nhfp);
+    save_bubbles(nhfp, lev); /* for water and air */
+
     if (nhfp->mode != FREEING) {
         if (nhfp->structlevel)
             bflush(nhfp->fd);
@@ -618,6 +643,28 @@ savelevl(NHFILE* nhfp, boolean rlecomp)
     return;
 }
 
+/* save Plane of Water's air bubbles and Plane of Air's clouds */
+static void
+save_bubbles(NHFILE *nhfp, xchar lev)
+{
+    xchar bbubbly;
+
+    /* air bubbles and clouds used to be saved as part of game state
+       because restoring them needs dungeon data that isn't available
+       during the first pass of their levels; now that they are part of
+       the current level instead, we write a zero or non-zero marker
+       so that restore can determine whether they are present even when
+       u.uz and ledger_no() aren't available to it yet */
+    bbubbly = 0;
+    if (lev == ledger_no(&water_level) || lev == ledger_no(&air_level))
+        bbubbly = lev; /* non-zero */
+    if (nhfp->structlevel)
+        bwrite(nhfp->fd, (genericptr_t) &bbubbly, sizeof bbubbly);
+
+    if (bbubbly)
+        save_waterlevel(nhfp); /* save air bubbles or clouds */
+}
+
 /* used when saving a level and also when saving dungeon overview data */
 void
 savecemetery(NHFILE* nhfp, struct cemetery** cemeteryaddr)
index c83f22749250b355f017e33b7ae1d356e2951c0d..08732c4ad81f6653d8f0e4ac1e5933c23be07bff 100644 (file)
@@ -228,7 +228,8 @@ mread(int fd, genericptr_t buf, unsigned len)
             restoreinfo.mread_flags = -1;
             return;
         } else {
-            pline("Read %d instead of %u bytes.", rlen, len);
+            pline("Read %d instead of %u bytes.", (int) rlen, len);
+            display_nhwindow(WIN_MESSAGE, TRUE); /* flush before error() */
             if (g.program_state.restoring) {
                 (void) nhclose(fd);
                 (void) delete_savefile();
index dbf1a9128906118f5227ce94ded4c20a491f6693..ae8dbf0e5b6bcb1cf8630cbd0056c9d0bd7c1bfa 100644 (file)
@@ -143,15 +143,28 @@ does_block(int x, int y, struct rm *lev)
     struct monst *mon;
     int i;
 
+#ifdef DEBUG
+    /* set DEBUGFILES=seethru in environment to see through bubbles */
+    if (g.seethru == 0) { /* init once */
+        g.seethru = (wizard && explicitdebug("seethru")) ? 1 : -1;
+    }
+#endif
+
     /* Features that block . . */
     if (IS_ROCK(lev->typ) || lev->typ == TREE
         || (IS_DOOR(lev->typ)
             && (lev->doormask & (D_CLOSED | D_LOCKED | D_TRAPPED))))
         return 1;
 
+#ifdef DEBUG
+    if (g.seethru != 1) {
+#endif
     if (lev->typ == CLOUD || IS_WATERWALL(lev->typ)
-        || (lev->typ == MOAT && Underwater))
+        || (Underwater && is_moat(x, y)))
         return 1;
+#ifdef DEBUG
+    } /* g.seethru */
+#endif
 
     /* Boulders block light. */
     for (obj = g.level.objects[x][y]; obj; obj = obj->nexthere)
@@ -163,6 +176,9 @@ does_block(int x, int y, struct rm *lev)
         && is_lightblocker_mappear(mon))
         return 1;
 
+#ifdef DEBUG
+    if (g.seethru != 1) {
+#endif
     /* Clouds (poisonous or not) block light. */
     for (i = 0; i < g.n_regions; i++) {
         /* Ignore regions with ttl == 0 - expire_gas_cloud must unblock its
@@ -172,6 +188,9 @@ does_block(int x, int y, struct rm *lev)
             return 1;
         }
     }
+#ifdef DEBUG
+    } /* g.seethru */
+#endif
 
     return 0;
 }
@@ -828,6 +847,17 @@ vision_recalc(int control)
 void
 block_point(int x, int y)
 {
+#ifdef DEBUG
+    /* set DEBUGFILES=seethru in environment to see through clouds & water */
+    if (g.seethru == 0) { /* init once */
+        g.seethru = (wizard && explicitdebug("seethru")) ? 1 : -1;
+    }
+    if (g.seethru == 1) {
+        if (!does_block(x, y, &levl[x][y]))
+            return;
+    }
+#endif
+
     fill_point(y, x);
 
     /* recalc light sources here? */