bubble/cloud save/restore overhaul

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....
This commit is contained in:
PatR
2022-04-29 12:44:26 -07:00
parent 5e1fba6e29
commit 5d56da3d32
10 changed files with 137 additions and 49 deletions

View 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 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 for force-fight against edge of level, report "you harmlessly attack unknown
obstacle" rather than "you have moved as far <direction> as possible" 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 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 inadvertently prevented them from being used up when Riders revived
force-fight against furniture covered by an object described the attack as force-fight against furniture covered by an object described the attack as
missing an unknown obstacle 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 curses: 'msg_window' option wasn't functional for curses unless the binary
also included tty support 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 allow setting msgtype in SOUND line
display detected door traps and chest traps as trapped doors and trapped display detected door traps and chest traps as trapped doors and trapped
chests rather than as fake bear traps 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 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 file called monsters.h, then taking advantage of the C preprocessor to
generate appropriate enum values during compile generate appropriate enum values during compile
onames.h dependency and inclusion is removed and comparable functionality is 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 file called objects.h, then taking advantage of the C preprocessor to
generate appropriate enum values during compile generate appropriate enum values during compile
artilist.h is used to generate appropriate artifact enum values by the C 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 add git submodule support to the Makefiles by specifying git=1 or GIT=1 on the
make command make command
add TT_NONE==0, renumber other u.utraptype values so that TT_BEARTRAP isn't 0 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

View File

@@ -1165,6 +1165,7 @@ struct instance_globals {
unsigned usteed_id; /* need to preserve during save */ unsigned usteed_id; /* need to preserve during save */
struct obj *looseball; /* track uball during save and... */ struct obj *looseball; /* track uball during save and... */
struct obj *loosechain; /* track uchain since saving might free it */ struct obj *loosechain; /* track uchain since saving might free it */
d_level uz_save;
/* shk.c */ /* shk.c */
/* auto-response flag for/from "sell foo?" 'a' => 'y', 'q' => 'n' */ /* 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_rmin; /* min could see indices */
xchar *viz_rmax; /* max could see indices */ xchar *viz_rmax; /* max could see indices */
boolean vision_full_recalc; boolean vision_full_recalc;
int seethru; /* 'bubble' debugging: clouds and water don't block light */
/* weapon.c */ /* weapon.c */
struct obj *propellor; struct obj *propellor;

View File

@@ -17,7 +17,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 55 #define EDITLEVEL 56
/* /*
* Development status possibilities. * Development status possibilities.

View File

@@ -615,6 +615,7 @@ const struct instance_globals g_init = {
0, /* usteed_id */ 0, /* usteed_id */
(struct obj *) 0, /* looseball */ (struct obj *) 0, /* looseball */
(struct obj *) 0, /* loosechain */ (struct obj *) 0, /* loosechain */
{ 0, 0 }, /* uz_save */
/* shk.c */ /* shk.c */
'a', /* sell_response */ 'a', /* sell_response */
@@ -670,6 +671,7 @@ const struct instance_globals g_init = {
NULL, /* viz_rmin */ NULL, /* viz_rmin */
NULL, /* viz_rmax */ NULL, /* viz_rmax */
FALSE, /* vision_full_recalc */ FALSE, /* vision_full_recalc */
0, /* seethru */
/* weapon.c */ /* weapon.c */
UNDEFINED_PTR, /* propellor */ UNDEFINED_PTR, /* propellor */

View File

@@ -1444,22 +1444,6 @@ goto_level(
nhfp->mode = cant_go_back ? FREEING : (WRITING | FREEING); nhfp->mode = cant_go_back ? FREEING : (WRITING | FREEING);
savelev(nhfp, ledger_no(&u.uz)); savelev(nhfp, ledger_no(&u.uz));
nhfp->mode = save_mode; 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); close_nhfile(nhfp);
if (cant_go_back) { if (cant_go_back) {
/* discard unreachable levels; keep #0 */ /* discard unreachable levels; keep #0 */
@@ -1517,16 +1501,6 @@ goto_level(
reseed_random(rn2_on_display_rng); reseed_random(rn2_on_display_rng);
minit(); /* ZEROCOMP */ minit(); /* ZEROCOMP */
getlev(nhfp, g.hackpid, new_ledger); 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); close_nhfile(nhfp);
oinit(); /* reassign level dependent obj probabilities */ oinit(); /* reassign level dependent obj probabilities */
} }

View File

@@ -1592,7 +1592,7 @@ save_waterlevel(NHFILE* nhfp)
{ {
struct bubble *b; struct bubble *b;
if (!Is_waterlevel(&u.uz) && !Is_airlevel(&u.uz)) if (!g.bbubbles)
return; return;
if (perform_bwrite(nhfp)) { if (perform_bwrite(nhfp)) {
@@ -1621,17 +1621,6 @@ restore_waterlevel(NHFILE* nhfp)
struct bubble *b = (struct bubble *) 0, *btmp; struct bubble *b = (struct bubble *) 0, *btmp;
int i, n = 0; 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(); set_wportal();
if (nhfp->structlevel) { if (nhfp->structlevel) {
mread(nhfp->fd,(genericptr_t)&n,sizeof(int)); mread(nhfp->fd,(genericptr_t)&n,sizeof(int));
@@ -1655,7 +1644,21 @@ restore_waterlevel(NHFILE* nhfp)
mv_bubble(b, 0, 0, TRUE); mv_bubble(b, 0, 0, TRUE);
} }
g.ebubbles = b; 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 static void

View File

@@ -30,6 +30,7 @@ static void ghostfruit(struct obj *);
static boolean restgamestate(NHFILE *, unsigned int *, unsigned int *); static boolean restgamestate(NHFILE *, unsigned int *, unsigned int *);
static void restlevelstate(unsigned int, unsigned int); static void restlevelstate(unsigned int, unsigned int);
static int restlevelfile(xchar); static int restlevelfile(xchar);
static void rest_bubbles(NHFILE *);
static void restore_gamelog(NHFILE *); static void restore_gamelog(NHFILE *);
static void restore_msghistory(NHFILE *); static void restore_msghistory(NHFILE *);
static void reset_oattached_mids(boolean); static void reset_oattached_mids(boolean);
@@ -683,7 +684,6 @@ restgamestate(NHFILE* nhfp, unsigned int* stuckid, unsigned int* steedid)
g.ffruit = loadfruitchn(nhfp); g.ffruit = loadfruitchn(nhfp);
restnames(nhfp); restnames(nhfp);
restore_waterlevel(nhfp);
restore_msghistory(nhfp); restore_msghistory(nhfp);
restore_gamelog(nhfp); restore_gamelog(nhfp);
/* must come after all mons & objs are restored */ /* must come after all mons & objs are restored */
@@ -1127,6 +1127,7 @@ getlev(NHFILE* nhfp, int pid, xchar lev)
} }
restdamage(nhfp); restdamage(nhfp);
rest_regions(nhfp); rest_regions(nhfp);
rest_bubbles(nhfp); /* for water and air; empty marker on other levels */
if (ghostly) { if (ghostly) {
stairway *stway = g.stairs; stairway *stway = g.stairs;
@@ -1217,6 +1218,23 @@ get_plname_from_file(NHFILE* nhfp, char *plbuf)
return; 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 static void
restore_gamelog(NHFILE* nhfp) restore_gamelog(NHFILE* nhfp)
{ {

View File

@@ -19,6 +19,7 @@ int dotcnt, dotrow; /* also used in restore */
static void savelevchn(NHFILE *); static void savelevchn(NHFILE *);
static void savelevl(NHFILE *,boolean); static void savelevl(NHFILE *,boolean);
static void savedamage(NHFILE *); static void savedamage(NHFILE *);
static void save_bubbles(NHFILE *, xchar);
static void save_stairs(NHFILE *); static void save_stairs(NHFILE *);
static void saveobj(NHFILE *,struct obj *); static void saveobj(NHFILE *,struct obj *);
static void saveobjchn(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 savetrapchn(NHFILE *,struct trap *);
static void save_gamelog(NHFILE *); static void save_gamelog(NHFILE *);
static void savegamestate(NHFILE *); static void savegamestate(NHFILE *);
static void savelev_core(NHFILE *, xchar);
static void save_msghistory(NHFILE *); static void save_msghistory(NHFILE *);
#ifdef ZEROCOMP #ifdef ZEROCOMP
@@ -76,7 +78,6 @@ dosave0(void)
{ {
const char *fq_save; const char *fq_save;
xchar ltmp; xchar ltmp;
d_level uz_save;
char whynot[BUFSZ]; char whynot[BUFSZ];
NHFILE *nhfp, *onhfp; NHFILE *nhfp, *onhfp;
int res = 0; int res = 0;
@@ -174,8 +175,9 @@ dosave0(void)
* parts of the restore code from completely initializing all * parts of the restore code from completely initializing all
* in-core data structures, since all we're doing is copying. * in-core data structures, since all we're doing is copying.
* This also avoids at least one nasty core dump. * 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; u.uz.dnum = u.uz.dlevel = 0;
/* these pointers are no longer valid, and at least u.usteed /* these pointers are no longer valid, and at least u.usteed
* may mislead place_monster() on other levels * may mislead place_monster() on other levels
@@ -184,7 +186,7 @@ dosave0(void)
u.usteed = (struct monst *) 0; u.usteed = (struct monst *) 0;
for (ltmp = (xchar) 1; ltmp <= maxledgerno(); ltmp++) { for (ltmp = (xchar) 1; ltmp <= maxledgerno(); ltmp++) {
if (ltmp == ledger_no(&uz_save)) if (ltmp == ledger_no(&g.uz_save))
continue; continue;
if (!(g.level_info[ltmp].flags & LFILE_EXISTS)) if (!(g.level_info[ltmp].flags & LFILE_EXISTS))
continue; continue;
@@ -218,7 +220,8 @@ dosave0(void)
} }
close_nhfile(nhfp); 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 */ /* get rid of current level --jgm */
delete_levelfile(ledger_no(&u.uz)); delete_levelfile(ledger_no(&u.uz));
@@ -349,7 +352,6 @@ savegamestate(NHFILE* nhfp)
} }
savefruitchn(nhfp); savefruitchn(nhfp);
savenames(nhfp); savenames(nhfp);
save_waterlevel(nhfp);
save_msghistory(nhfp); save_msghistory(nhfp);
save_gamelog(nhfp); save_gamelog(nhfp);
if (nhfp->structlevel) if (nhfp->structlevel)
@@ -457,6 +459,27 @@ savestateinlock(void)
void void
savelev(NHFILE *nhfp, xchar lev) 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 #ifdef TOS
short tlev; short tlev;
@@ -544,6 +567,8 @@ savelev(NHFILE *nhfp, xchar lev)
save_engravings(nhfp); save_engravings(nhfp);
savedamage(nhfp); /* pending shop wall and/or floor repair */ savedamage(nhfp); /* pending shop wall and/or floor repair */
save_regions(nhfp); save_regions(nhfp);
save_bubbles(nhfp, lev); /* for water and air */
if (nhfp->mode != FREEING) { if (nhfp->mode != FREEING) {
if (nhfp->structlevel) if (nhfp->structlevel)
bflush(nhfp->fd); bflush(nhfp->fd);
@@ -618,6 +643,28 @@ savelevl(NHFILE* nhfp, boolean rlecomp)
return; 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 */ /* used when saving a level and also when saving dungeon overview data */
void void
savecemetery(NHFILE* nhfp, struct cemetery** cemeteryaddr) savecemetery(NHFILE* nhfp, struct cemetery** cemeteryaddr)

View File

@@ -228,7 +228,8 @@ mread(int fd, genericptr_t buf, unsigned len)
restoreinfo.mread_flags = -1; restoreinfo.mread_flags = -1;
return; return;
} else { } 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) { if (g.program_state.restoring) {
(void) nhclose(fd); (void) nhclose(fd);
(void) delete_savefile(); (void) delete_savefile();

View File

@@ -143,15 +143,28 @@ does_block(int x, int y, struct rm *lev)
struct monst *mon; struct monst *mon;
int i; 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 . . */ /* Features that block . . */
if (IS_ROCK(lev->typ) || lev->typ == TREE if (IS_ROCK(lev->typ) || lev->typ == TREE
|| (IS_DOOR(lev->typ) || (IS_DOOR(lev->typ)
&& (lev->doormask & (D_CLOSED | D_LOCKED | D_TRAPPED)))) && (lev->doormask & (D_CLOSED | D_LOCKED | D_TRAPPED))))
return 1; return 1;
#ifdef DEBUG
if (g.seethru != 1) {
#endif
if (lev->typ == CLOUD || IS_WATERWALL(lev->typ) if (lev->typ == CLOUD || IS_WATERWALL(lev->typ)
|| (lev->typ == MOAT && Underwater)) || (Underwater && is_moat(x, y)))
return 1; return 1;
#ifdef DEBUG
} /* g.seethru */
#endif
/* Boulders block light. */ /* Boulders block light. */
for (obj = g.level.objects[x][y]; obj; obj = obj->nexthere) 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)) && is_lightblocker_mappear(mon))
return 1; return 1;
#ifdef DEBUG
if (g.seethru != 1) {
#endif
/* Clouds (poisonous or not) block light. */ /* Clouds (poisonous or not) block light. */
for (i = 0; i < g.n_regions; i++) { for (i = 0; i < g.n_regions; i++) {
/* Ignore regions with ttl == 0 - expire_gas_cloud must unblock its /* 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; return 1;
} }
} }
#ifdef DEBUG
} /* g.seethru */
#endif
return 0; return 0;
} }
@@ -828,6 +847,17 @@ vision_recalc(int control)
void void
block_point(int x, int y) 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); fill_point(y, x);
/* recalc light sources here? */ /* recalc light sources here? */