Files
nethack/src/restore.c
nethack.rankin 039e051323 mextra tweaks (trunk only)
Add new_mname/free_mname functions to make monster name handling be
more like the other extended data and to hide mextra details a bit more.
Add some casts where int and unsigned are being intermixed.  Simplify
christen_monst(); it ought to be changed to have type `void' but I wanted
to avoid modifying another ten or so files.
2006-01-08 06:19:42 +00:00

1486 lines
37 KiB
C

/* SCCS Id: @(#)restore.c 3.5 2005/12/14 */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "lev.h"
#include "tcap.h" /* for TERMLIB and ASCIIGRAPH */
#if defined(MICRO)
extern int dotcnt; /* shared with save */
extern int dotrow; /* shared with save */
#endif
#ifdef USE_TILES
extern void FDECL(substitute_tiles, (d_level *)); /* from tile.c */
#endif
#ifdef ZEROCOMP
STATIC_DCL void NDECL(zerocomp_minit);
STATIC_DCL void FDECL(zerocomp_mread, (int,genericptr_t,unsigned int));
STATIC_DCL int NDECL(zerocomp_mgetc);
#endif
STATIC_DCL void NDECL(def_minit);
STATIC_DCL void FDECL(def_mread, (int,genericptr_t,unsigned int));
STATIC_DCL void NDECL(find_lev_obj);
STATIC_DCL void FDECL(restlevchn, (int));
STATIC_DCL void FDECL(restdamage, (int,BOOLEAN_P));
STATIC_DCL struct obj *FDECL(restobjchn, (int,BOOLEAN_P,BOOLEAN_P));
STATIC_DCL struct monst *FDECL(restmonchn, (int,BOOLEAN_P));
STATIC_DCL struct fruit *FDECL(loadfruitchn, (int));
STATIC_DCL void FDECL(freefruitchn, (struct fruit *));
STATIC_DCL void FDECL(ghostfruit, (struct obj *));
STATIC_DCL boolean FDECL(restgamestate, (int, unsigned int *, unsigned int *));
STATIC_DCL void FDECL(restlevelstate, (unsigned int, unsigned int));
STATIC_DCL int FDECL(restlevelfile, (int,XCHAR_P));
STATIC_OVL void FDECL(restore_msghistory, (int));
STATIC_DCL void FDECL(reset_oattached_mids, (BOOLEAN_P));
#ifndef GOLDOBJ
STATIC_DCL struct obj *FDECL(gold_in, (struct obj *));
#endif
STATIC_DCL void FDECL(rest_levl, (int,BOOLEAN_P));
static struct restore_procs {
const char *name;
int mread_flags;
void NDECL((*restore_minit));
void FDECL((*restore_mread), (int,genericptr_t,unsigned int));
void FDECL((*restore_bclose), (int));
} restoreprocs = {
#if !defined(ZEROCOMP) || (defined(COMPRESS) || defined(ZLIB_COMP))
"externalcomp", 0,
def_minit,
def_mread,
def_bclose,
#else
"zerocomp", 0,
zerocomp_minit,
zerocomp_mread,
zerocomp_bclose,
#endif
};
/*
* Save a mapping of IDs from ghost levels to the current level. This
* map is used by the timer routines when restoring ghost levels.
*/
#define N_PER_BUCKET 64
struct bucket {
struct bucket *next;
struct {
unsigned gid; /* ghost ID */
unsigned nid; /* new ID */
} map[N_PER_BUCKET];
};
STATIC_DCL void NDECL(clear_id_mapping);
STATIC_DCL void FDECL(add_id_mapping, (unsigned, unsigned));
static int n_ids_mapped = 0;
static struct bucket *id_map = 0;
#ifdef AMII_GRAPHICS
void FDECL( amii_setpens, (int) ); /* use colors from save file */
extern int amii_numcolors;
#endif
boolean restoring = FALSE;
static NEARDATA struct fruit *oldfruit;
static NEARDATA long omoves;
#define Is_IceBox(o) ((o)->otyp == ICE_BOX ? TRUE : FALSE)
/* Recalculate level.objects[x][y], since this info was not saved. */
STATIC_OVL void
find_lev_obj()
{
register struct obj *fobjtmp = (struct obj *)0;
register struct obj *otmp;
int x,y;
for(x=0; x<COLNO; x++) for(y=0; y<ROWNO; y++)
level.objects[x][y] = (struct obj *)0;
/*
* Reverse the entire fobj chain, which is necessary so that we can
* place the objects in the proper order. Make all obj in chain
* OBJ_FREE so place_object will work correctly.
*/
while ((otmp = fobj) != 0) {
fobj = otmp->nobj;
otmp->nobj = fobjtmp;
otmp->where = OBJ_FREE;
fobjtmp = otmp;
}
/* fobj should now be empty */
/* Set level.objects (as well as reversing the chain back again) */
while ((otmp = fobjtmp) != 0) {
fobjtmp = otmp->nobj;
place_object(otmp, otmp->ox, otmp->oy);
}
}
/* Things that were marked "in_use" when the game was saved (ex. via the
* infamous "HUP" cheat) get used up here.
*/
void
inven_inuse(quietly)
boolean quietly;
{
register struct obj *otmp, *otmp2;
for (otmp = invent; otmp; otmp = otmp2) {
otmp2 = otmp->nobj;
#ifndef GOLDOBJ
if (otmp->oclass == COIN_CLASS) {
/* in_use gold is created by some menu operations */
if (!otmp->in_use) {
impossible("inven_inuse: !in_use gold in inventory");
}
extract_nobj(otmp, &invent);
otmp->in_use = FALSE;
dealloc_obj(otmp);
} else
#endif /* GOLDOBJ */
if (otmp->in_use) {
if (!quietly) pline("Finishing off %s...", xname(otmp));
useup(otmp);
}
}
}
STATIC_OVL void
restlevchn(fd)
register int fd;
{
int cnt;
s_level *tmplev, *x;
sp_levchn = (s_level *) 0;
mread(fd, (genericptr_t) &cnt, sizeof(int));
for(; cnt > 0; cnt--) {
tmplev = (s_level *)alloc(sizeof(s_level));
mread(fd, (genericptr_t) tmplev, sizeof(s_level));
if(!sp_levchn) sp_levchn = tmplev;
else {
for(x = sp_levchn; x->next; x = x->next);
x->next = tmplev;
}
tmplev->next = (s_level *)0;
}
}
STATIC_OVL void
restdamage(fd, ghostly)
int fd;
boolean ghostly;
{
int counter;
struct damage *tmp_dam;
mread(fd, (genericptr_t) &counter, sizeof(counter));
if (!counter)
return;
tmp_dam = (struct damage *)alloc(sizeof(struct damage));
while (--counter >= 0) {
char damaged_shops[5], *shp = (char *)0;
mread(fd, (genericptr_t) tmp_dam, sizeof(*tmp_dam));
if (ghostly)
tmp_dam->when += (monstermoves - omoves);
Strcpy(damaged_shops,
in_rooms(tmp_dam->place.x, tmp_dam->place.y, SHOPBASE));
if (u.uz.dlevel) {
/* when restoring, there are two passes over the current
* level. the first time, u.uz isn't set, so neither is
* shop_keeper(). just wait and process the damage on
* the second pass.
*/
for (shp = damaged_shops; *shp; shp++) {
struct monst *shkp = shop_keeper(*shp);
if (shkp && inhishop(shkp) &&
repair_damage(shkp, tmp_dam, TRUE))
break;
}
}
if (!shp || !*shp) {
tmp_dam->next = level.damagelist;
level.damagelist = tmp_dam;
tmp_dam = (struct damage *)alloc(sizeof(*tmp_dam));
}
}
free((genericptr_t)tmp_dam);
}
STATIC_OVL struct obj *
restobjchn(fd, ghostly, frozen)
register int fd;
boolean ghostly, frozen;
{
register struct obj *otmp, *otmp2 = 0;
register struct obj *first = (struct obj *)0;
int xl;
while(1) {
mread(fd, (genericptr_t) &xl, sizeof(xl));
if(xl == -1) break;
otmp = newobj(xl);
if(!first) first = otmp;
else otmp2->nobj = otmp;
mread(fd, (genericptr_t) otmp,
(unsigned) xl + sizeof(struct obj));
if (ghostly) {
unsigned nid = context.ident++;
add_id_mapping(otmp->o_id, nid);
otmp->o_id = nid;
}
if (ghostly && otmp->otyp == SLIME_MOLD) ghostfruit(otmp);
/* Ghost levels get object age shifted from old player's clock
* to new player's clock. Assumption: new player arrived
* immediately after old player died.
*/
if (ghostly && !frozen && !age_is_relative(otmp))
otmp->age = monstermoves - omoves + otmp->age;
/* get contents of a container or statue */
if (Has_contents(otmp)) {
struct obj *otmp3;
otmp->cobj = restobjchn(fd, ghostly, Is_IceBox(otmp));
/* restore container back pointers */
for (otmp3 = otmp->cobj; otmp3; otmp3 = otmp3->nobj)
otmp3->ocontainer = otmp;
}
if (otmp->bypass) otmp->bypass = 0;
if (!ghostly) {
/* fix the pointers */
if (otmp->o_id == context.victual.o_id)
context.victual.piece = otmp;
if (otmp->o_id == context.tin.o_id)
context.tin.tin = otmp;
if (otmp->o_id == context.spbook.o_id)
context.spbook.book = otmp;
}
otmp2 = otmp;
}
if(first && otmp2->nobj){
impossible("Restobjchn: error reading objchn.");
otmp2->nobj = 0;
}
return(first);
}
/*
* used by get_mtraits() in mkobj.c
* to retrieve the bundled up OATTACHED_MONST info.
*/
struct monst *
buffer_to_mon(buffer)
genericptr_t buffer;
{
int lth;
struct monst *mtmp;
char *spot = (char *)buffer;
mtmp = newmonst();
(void) memcpy((genericptr_t)mtmp, (genericptr_t)spot, sizeof(struct monst));
spot += sizeof(struct monst);
/* obtain the stored length of the monster name */
(void) memcpy((genericptr_t)&lth, (genericptr_t)spot, sizeof(int));
spot += sizeof(int);
if (lth) {
new_mname(mtmp, lth);
(void) memcpy((genericptr_t)MNAME(mtmp),
(genericptr_t)spot, lth);
spot += lth;
}
/* obtain the length of the egd (vault guard) structure */
(void) memcpy((genericptr_t)&lth, (genericptr_t)spot, sizeof(int));
spot += sizeof(int);
if (lth) {
newegd(mtmp);
(void) memcpy((genericptr_t)EGD(mtmp),
(genericptr_t)spot, lth);
spot += lth;
}
/* obtain the length of the epri (priest) structure */
(void) memcpy((genericptr_t)&lth, (genericptr_t)spot, sizeof(int));
spot += sizeof(int);
if (lth) {
newepri(mtmp);
(void) memcpy((genericptr_t)EPRI(mtmp),
(genericptr_t)spot, lth);
spot += lth;
}
/* obtain the length of the eshk (shopkeeper) structure */
(void) memcpy((genericptr_t)&lth, (genericptr_t)spot, sizeof(int));
spot += sizeof(int);
if (lth) {
neweshk(mtmp);
(void) memcpy((genericptr_t)ESHK(mtmp),
(genericptr_t)spot, lth);
spot += lth;
}
/* obtain the length of the emin (minion) structure */
(void) memcpy((genericptr_t)&lth, (genericptr_t)spot, sizeof(int));
spot += sizeof(int);
if (lth) {
newemin(mtmp);
(void) memcpy((genericptr_t)EMIN(mtmp),
(genericptr_t)spot, lth);
spot += lth;
}
/* obtain the length of the edog (mtame) structure */
(void) memcpy((genericptr_t)&lth, (genericptr_t)spot, sizeof(int));
spot += sizeof(int);
if (lth) {
newedog(mtmp);
(void) memcpy((genericptr_t)EDOG(mtmp),
(genericptr_t)spot, lth);
spot += lth; /* actually not necessary */
}
return mtmp;
}
STATIC_OVL struct monst *
restmonchn(fd, ghostly)
register int fd;
boolean ghostly;
{
register struct monst *mtmp, *mtmp2 = 0;
register struct monst *first = (struct monst *)0;
int offset, buflen;
while(1) {
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if(buflen == -1) break;
mtmp = newmonst();
mread(fd, (genericptr_t) mtmp, sizeof(struct monst));
/* any saved mextra pointer is invalid */
mtmp->mextra = (struct mextra *)0;
/* read the length of the name and the name */
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if (buflen > 0) {
new_mname(mtmp, buflen);
mread(fd, (genericptr_t) MNAME(mtmp), buflen);
}
/* egd */
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if (buflen > 0) {
newegd(mtmp);
mread(fd, (genericptr_t) EGD(mtmp),
sizeof(struct egd));
}
/* epri */
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if (buflen > 0) {
newepri(mtmp);
mread(fd, (genericptr_t) EPRI(mtmp),
sizeof(struct epri));
}
/* eshk */
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if (buflen > 0) {
neweshk(mtmp);
mread(fd, (genericptr_t) ESHK(mtmp),
sizeof(struct eshk));
}
/* emin */
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if (buflen > 0) {
newemin(mtmp);
mread(fd, (genericptr_t) EMIN(mtmp),
sizeof(struct emin));
}
/* edog */
mread(fd, (genericptr_t) &buflen, sizeof(buflen));
if (buflen > 0) {
newedog(mtmp);
mread(fd, (genericptr_t) EDOG(mtmp),
sizeof(struct edog));
}
if(!first) first = mtmp;
else mtmp2->nmon = mtmp;
if (ghostly) {
unsigned nid = context.ident++;
add_id_mapping(mtmp->m_id, nid);
mtmp->m_id = nid;
}
offset = mtmp->mnum;
mtmp->data = &mons[offset];
if (ghostly) {
int mndx = monsndx(mtmp->data);
if (propagate(mndx, TRUE, ghostly) == 0) {
/* cookie to trigger purge in getbones() */
mtmp->mhpmax = DEFUNCT_MONSTER;
}
}
if(mtmp->minvent) {
struct obj *obj;
mtmp->minvent = restobjchn(fd, ghostly, FALSE);
/* restore monster back pointer */
for (obj = mtmp->minvent; obj; obj = obj->nobj)
obj->ocarry = mtmp;
#ifndef GOLDOBJ
put_gold_back(&mtmp->minvent, &mtmp->mgold);
#endif
}
if (mtmp->mw) {
struct obj *obj;
for(obj = mtmp->minvent; obj; obj = obj->nobj)
if (obj->owornmask & W_WEP) break;
if (obj) mtmp->mw = obj;
else {
MON_NOWEP(mtmp);
impossible("bad monster weapon restore");
}
}
if (mtmp->isshk) restshk(mtmp, ghostly);
if (mtmp->ispriest) restpriest(mtmp, ghostly);
mtmp2 = mtmp;
}
if(first && mtmp2->nmon){
impossible("Restmonchn: error reading monchn.");
mtmp2->nmon = 0;
}
return(first);
}
STATIC_OVL struct fruit *
loadfruitchn(fd)
int fd;
{
register struct fruit *flist, *fnext;
flist = 0;
while (fnext = newfruit(),
mread(fd, (genericptr_t)fnext, sizeof *fnext),
fnext->fid != 0) {
fnext->nextf = flist;
flist = fnext;
}
dealloc_fruit(fnext);
return flist;
}
STATIC_OVL void
freefruitchn(flist)
register struct fruit *flist;
{
register struct fruit *fnext;
while (flist) {
fnext = flist->nextf;
dealloc_fruit(flist);
flist = fnext;
}
}
STATIC_OVL void
ghostfruit(otmp)
register struct obj *otmp;
{
register struct fruit *oldf;
for (oldf = oldfruit; oldf; oldf = oldf->nextf)
if (oldf->fid == otmp->spe) break;
if (!oldf) impossible("no old fruit?");
else otmp->spe = fruitadd(oldf->fname);
}
STATIC_OVL
boolean
restgamestate(fd, stuckid, steedid)
register int fd;
unsigned int *stuckid, *steedid; /* STEED */
{
/* discover is actually flags.explore */
boolean remember_discover = discover;
struct obj *otmp;
int uid;
mread(fd, (genericptr_t) &uid, sizeof uid);
if (uid != getuid()) { /* strange ... */
/* for wizard mode, issue a reminder; for others, treat it
as an attempt to cheat and refuse to restore this file */
pline("Saved game was not yours.");
#ifdef WIZARD
if (!wizard)
#endif
return FALSE;
}
mread(fd, (genericptr_t) &context, sizeof(struct context_info));
if (context.warntype.speciesidx)
context.warntype.species = &mons[context.warntype.speciesidx];
mread(fd, (genericptr_t) &flags, sizeof(struct flag));
if (remember_discover) discover = remember_discover;
#ifdef SYSFLAGS
mread(fd, (genericptr_t) &sysflags, sizeof(struct sysflag));
#endif
role_init(); /* Reset the initial role, race, gender, and alignment */
#ifdef AMII_GRAPHICS
amii_setpens(amii_numcolors); /* use colors from save file */
#endif
mread(fd, (genericptr_t) &u, sizeof(struct you));
set_uasmon();
#ifdef CLIPPING
cliparound(u.ux, u.uy);
#endif
if(u.uhp <= 0 && (!Upolyd || u.mh <= 0)) {
u.ux = u.uy = 0; /* affects pline() [hence You()] */
You("were not healthy enough to survive restoration.");
/* wiz1_level.dlevel is used by mklev.c to see if lots of stuff is
* uninitialized, so we only have to set it and not the other stuff.
*/
wiz1_level.dlevel = 0;
u.uz.dnum = 0;
u.uz.dlevel = 1;
return(FALSE);
}
/* this stuff comes after potential aborted restore attempts */
restore_killers(fd);
restore_timers(fd, RANGE_GLOBAL, FALSE, 0L);
restore_light_sources(fd);
invent = restobjchn(fd, FALSE, FALSE);
#ifndef GOLDOBJ
put_gold_back(&invent, &u.ugold);
#endif
migrating_objs = restobjchn(fd, FALSE, FALSE);
migrating_mons = restmonchn(fd, FALSE);
mread(fd, (genericptr_t) mvitals, sizeof(mvitals));
/*
* There are some things after this that can have unintended display
* side-effects too early in the game.
* Disable see_monsters() here, re-enable it at the top of moveloop()
*/
defer_see_monsters = TRUE;
/* this comes after inventory has been loaded */
for(otmp = invent; otmp; otmp = otmp->nobj)
if(otmp->owornmask)
setworn(otmp, otmp->owornmask);
/* reset weapon so that player will get a reminder about "bashing"
during next fight when bare-handed or wielding an unconventional
item; for pick-axe, we aren't able to distinguish between having
applied or wielded it, so be conservative and assume the former */
otmp = uwep; /* `uwep' usually init'd by setworn() in loop above */
uwep = 0; /* clear it and have setuwep() reinit */
setuwep(otmp); /* (don't need any null check here) */
if (!uwep || uwep->otyp == PICK_AXE || uwep->otyp == GRAPPLING_HOOK)
unweapon = TRUE;
restore_dungeon(fd);
restlevchn(fd);
mread(fd, (genericptr_t) &moves, sizeof moves);
mread(fd, (genericptr_t) &monstermoves, sizeof monstermoves);
mread(fd, (genericptr_t) &quest_status, sizeof(struct q_score));
mread(fd, (genericptr_t) spl_book,
sizeof(struct spell) * (MAXSPELL + 1));
restore_artifacts(fd);
restore_oracles(fd);
if (u.ustuck)
mread(fd, (genericptr_t) stuckid, sizeof (*stuckid));
#ifdef STEED
if (u.usteed)
mread(fd, (genericptr_t) steedid, sizeof (*steedid));
#endif
mread(fd, (genericptr_t) pl_character, sizeof pl_character);
mread(fd, (genericptr_t) pl_fruit, sizeof pl_fruit);
mread(fd, (genericptr_t) &current_fruit, sizeof current_fruit);
freefruitchn(ffruit); /* clean up fruit(s) made by initoptions() */
ffruit = loadfruitchn(fd);
restnames(fd);
restore_waterlevel(fd);
restore_msghistory(fd);
/* must come after all mons & objs are restored */
relink_timers(FALSE);
relink_light_sources(FALSE);
return(TRUE);
}
/* update game state pointers to those valid for the current level (so we
* don't dereference a wild u.ustuck when saving the game state, for instance)
*/
STATIC_OVL void
restlevelstate(stuckid, steedid)
unsigned int stuckid, steedid; /* STEED */
{
register struct monst *mtmp;
if (stuckid) {
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
if (mtmp->m_id == stuckid) break;
if (!mtmp) panic("Cannot find the monster ustuck.");
u.ustuck = mtmp;
}
#ifdef STEED
if (steedid) {
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
if (mtmp->m_id == steedid) break;
if (!mtmp) panic("Cannot find the monster usteed.");
u.usteed = mtmp;
remove_monster(mtmp->mx, mtmp->my);
}
#endif
}
/*ARGSUSED*/ /* fd used in MFLOPPY only */
STATIC_OVL int
restlevelfile(fd, ltmp)
register int fd;
xchar ltmp;
#if defined(macintosh) && (defined(__SC__) || defined(__MRC__))
# pragma unused(fd)
#endif
{
register int nfd;
char whynot[BUFSZ];
nfd = create_levelfile(ltmp, whynot);
if (nfd < 0) {
/* BUG: should suppress any attempt to write a panic
save file if file creation is now failing... */
panic("restlevelfile: %s", whynot);
}
#ifdef MFLOPPY
if (!savelev(nfd, ltmp, COUNT_SAVE)) {
/* The savelev can't proceed because the size required
* is greater than the available disk space.
*/
pline("Not enough space on `%s' to restore your game.",
levels);
/* Remove levels and bones that may have been created.
*/
(void) close(nfd);
# ifdef AMIGA
clearlocks();
# else
eraseall(levels, alllevels);
eraseall(levels, allbones);
/* Perhaps the person would like to play without a
* RAMdisk.
*/
if (ramdisk) {
/* PlaywoRAMdisk may not return, but if it does
* it is certain that ramdisk will be 0.
*/
playwoRAMdisk();
/* Rewind save file and try again */
(void) lseek(fd, (off_t)0, 0);
(void) validate(fd, (char *)0); /* skip version etc */
return dorecover(fd); /* 0 or 1 */
} else {
# endif
pline("Be seeing you...");
terminate(EXIT_SUCCESS);
# ifndef AMIGA
}
# endif
}
#endif
bufon(nfd);
savelev(nfd, ltmp, WRITE_SAVE | FREE_SAVE);
bclose(nfd);
return(2);
}
int
dorecover(fd)
register int fd;
{
unsigned int stuckid = 0, steedid = 0; /* not a register */
xchar ltmp;
int rtmp;
struct obj *otmp;
restoring = TRUE;
get_plname_from_file(fd, plname);
getlev(fd, 0, (xchar)0, FALSE);
if (!restgamestate(fd, &stuckid, &steedid)) {
display_nhwindow(WIN_MESSAGE, TRUE);
savelev(-1, 0, FREE_SAVE); /* discard current level */
(void) close(fd);
(void) delete_savefile();
restoring = FALSE;
return(0);
}
restlevelstate(stuckid, steedid);
#ifdef INSURANCE
savestateinlock();
#endif
rtmp = restlevelfile(fd, ledger_no(&u.uz));
if (rtmp < 2) return(rtmp); /* dorecover called recursively */
/* these pointers won't be valid while we're processing the
* other levels, but they'll be reset again by restlevelstate()
* afterwards, and in the meantime at least u.usteed may mislead
* place_monster() on other levels
*/
u.ustuck = (struct monst *)0;
#ifdef STEED
u.usteed = (struct monst *)0;
#endif
#ifdef MICRO
# ifdef AMII_GRAPHICS
{
extern struct window_procs amii_procs;
if(windowprocs.win_init_nhwindows== amii_procs.win_init_nhwindows){
extern winid WIN_BASE;
clear_nhwindow(WIN_BASE); /* hack until there's a hook for this */
}
}
# else
clear_nhwindow(WIN_MAP);
# endif
clear_nhwindow(WIN_MESSAGE);
You("return to level %d in %s%s.",
depth(&u.uz), dungeons[u.uz.dnum].dname,
flags.debug ? " while in debug mode" :
flags.explore ? " while in explore mode" : "");
curs(WIN_MAP, 1, 1);
dotcnt = 0;
dotrow = 2;
if (strncmpi("X11", windowprocs.name, 3))
putstr(WIN_MAP, 0, "Restoring:");
#endif
restoreprocs.mread_flags = 1; /* return despite error */
while(1) {
mread(fd, (genericptr_t) &ltmp, sizeof ltmp);
if (restoreprocs.mread_flags == -1)
break;
getlev(fd, 0, ltmp, FALSE);
#ifdef MICRO
curs(WIN_MAP, 1+dotcnt++, dotrow);
if (dotcnt >= (COLNO - 1)) {
dotrow++;
dotcnt = 0;
}
if (strncmpi("X11", windowprocs.name, 3)){
putstr(WIN_MAP, 0, ".");
}
mark_synch();
#endif
rtmp = restlevelfile(fd, ltmp);
if (rtmp < 2) return(rtmp); /* dorecover called recursively */
}
restoreprocs.mread_flags = 0;
#ifdef BSD
(void) lseek(fd, 0L, 0);
#else
(void) lseek(fd, (off_t)0, 0);
#endif
(void) validate(fd, (char *)0); /* skip version and savefile info */
get_plname_from_file(fd, plname);
getlev(fd, 0, (xchar)0, FALSE);
(void) close(fd);
/* Now set the restore settings to match the
* settings used by the save file output routines
*/
reset_restpref();
if (!wizard && !discover)
(void) delete_savefile();
#ifdef REINCARNATION
if (Is_rogue_level(&u.uz)) assign_rogue_graphics(TRUE);
#endif
#ifdef USE_TILES
substitute_tiles(&u.uz);
#endif
restlevelstate(stuckid, steedid);
#ifdef MFLOPPY
gameDiskPrompt();
#endif
max_rank_sz(); /* to recompute mrank_sz (botl.c) */
/* take care of iron ball & chain */
for(otmp = fobj; otmp; otmp = otmp->nobj)
if(otmp->owornmask)
setworn(otmp, otmp->owornmask);
/* in_use processing must be after:
* + The inventory has been read so that freeinv() works.
* + The current level has been restored so billing information
* is available.
*/
inven_inuse(FALSE);
load_qtlist(); /* re-load the quest text info */
/* Set up the vision internals, after levl[] data is loaded */
/* but before docrt(). */
vision_reset();
vision_full_recalc = 1; /* recompute vision (not saved) */
run_timers(); /* expire all timers that have gone off while away */
docrt();
restoring = FALSE;
clear_nhwindow(WIN_MESSAGE);
program_state.something_worth_saving++; /* useful data now exists */
/* Success! */
welcome(FALSE);
return(1);
}
STATIC_OVL void
rest_levl(fd, rlecomp)
int fd;
boolean rlecomp;
{
#ifdef RLECOMP
short i, j;
uchar len;
struct rm r;
if (rlecomp) {
# if defined(MAC)
/* Suppress warning about used before set */
(void) memset((genericptr_t) &r, 0, sizeof(r));
# endif
i = 0; j = 0; len = 0;
while(i < ROWNO) {
while(j < COLNO) {
if(len > 0) {
levl[j][i] = r;
len -= 1;
j += 1;
} else {
mread(fd, (genericptr_t)&len, sizeof(uchar));
mread(fd, (genericptr_t)&r, sizeof(struct rm));
}
}
j = 0;
i += 1;
}
} else
#endif /* RLECOMP */
mread(fd, (genericptr_t) levl, sizeof(levl));
}
void
trickery(reason)
char *reason;
{
pline("Strange, this map is not as I remember it.");
pline("Somebody is trying some trickery here...");
pline("This game is void.");
Strcpy(killer.name, reason ? reason : "");
done(TRICKED);
}
void
getlev(fd, pid, lev, ghostly)
int fd, pid;
xchar lev;
boolean ghostly;
{
register struct trap *trap;
register struct monst *mtmp;
branch *br;
int hpid;
xchar dlvl;
int x, y;
#ifdef TOS
short tlev;
#endif
if (ghostly)
clear_id_mapping();
#if defined(MSDOS) || defined(OS2)
setmode(fd, O_BINARY);
#endif
/* Load the old fruit info. We have to do it first, so the
* information is available when restoring the objects.
*/
if (ghostly) oldfruit = loadfruitchn(fd);
/* First some sanity checks */
mread(fd, (genericptr_t) &hpid, sizeof(hpid));
/* CHECK: This may prevent restoration */
#ifdef TOS
mread(fd, (genericptr_t) &tlev, sizeof(tlev));
dlvl=tlev&0x00ff;
#else
mread(fd, (genericptr_t) &dlvl, sizeof(dlvl));
#endif
if ((pid && pid != hpid) || (lev && dlvl != lev)) {
char trickbuf[BUFSZ];
if (pid && pid != hpid)
Sprintf(trickbuf, "PID (%d) doesn't match saved PID (%d)!",
hpid, pid);
else
Sprintf(trickbuf, "This is level %d, not %d!", dlvl, lev);
#ifdef WIZARD
if (wizard) pline(trickbuf);
#endif
trickery(trickbuf);
}
rest_levl(fd, (boolean)((sfrestinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP));
mread(fd, (genericptr_t)&omoves, sizeof(omoves));
mread(fd, (genericptr_t)&upstair, sizeof(stairway));
mread(fd, (genericptr_t)&dnstair, sizeof(stairway));
mread(fd, (genericptr_t)&upladder, sizeof(stairway));
mread(fd, (genericptr_t)&dnladder, sizeof(stairway));
mread(fd, (genericptr_t)&sstairs, sizeof(stairway));
mread(fd, (genericptr_t)&updest, sizeof(dest_area));
mread(fd, (genericptr_t)&dndest, sizeof(dest_area));
mread(fd, (genericptr_t)&level.flags, sizeof(level.flags));
mread(fd, (genericptr_t)doors, sizeof(doors));
rest_rooms(fd); /* No joke :-) */
if (nroom)
doorindex = rooms[nroom - 1].fdoor + rooms[nroom - 1].doorct;
else
doorindex = 0;
restore_timers(fd, RANGE_LEVEL, ghostly, monstermoves - omoves);
restore_light_sources(fd);
fmon = restmonchn(fd, ghostly);
/* regenerate animals while on another level */
if (u.uz.dlevel) {
register struct monst *mtmp2;
for (mtmp = fmon; mtmp; mtmp = mtmp2) {
mtmp2 = mtmp->nmon;
if (ghostly) {
/* reset peaceful/malign relative to new character */
/* shopkeepers will reset based on name */
if(!mtmp->isshk) {
if (is_unicorn(mtmp->data) &&
sgn(u.ualign.type) == sgn(mtmp->data->maligntyp))
mtmp->mpeaceful = TRUE;
else
mtmp->mpeaceful = peace_minded(mtmp->data);
}
set_malign(mtmp);
} else if (monstermoves > omoves)
mon_catchup_elapsed_time(mtmp, monstermoves - omoves);
/* update shape-changers in case protection against
them is different now than when the level was saved */
restore_cham(mtmp);
}
}
rest_worm(fd); /* restore worm information */
ftrap = 0;
while (trap = newtrap(),
mread(fd, (genericptr_t)trap, sizeof(struct trap)),
trap->tx != 0) { /* need "!= 0" to work around DICE 3.0 bug */
trap->ntrap = ftrap;
ftrap = trap;
}
dealloc_trap(trap);
fobj = restobjchn(fd, ghostly, FALSE);
find_lev_obj();
/* restobjchn()'s `frozen' argument probably ought to be a callback
routine so that we can check for objects being buried under ice */
level.buriedobjlist = restobjchn(fd, ghostly, FALSE);
billobjs = restobjchn(fd, ghostly, FALSE);
rest_engravings(fd);
/* reset level.monsters for new level */
for (x = 0; x < COLNO; x++)
for (y = 0; y < ROWNO; y++)
level.monsters[x][y] = (struct monst *) 0;
for (mtmp = level.monlist; mtmp; mtmp = mtmp->nmon) {
if (mtmp->isshk)
set_residency(mtmp, FALSE);
place_monster(mtmp, mtmp->mx, mtmp->my);
if (mtmp->wormno) place_wsegs(mtmp);
}
restdamage(fd, ghostly);
rest_regions(fd, ghostly);
if (ghostly) {
/* Now get rid of all the temp fruits... */
freefruitchn(oldfruit), oldfruit = 0;
if (lev > ledger_no(&medusa_level) &&
lev < ledger_no(&stronghold_level) && xdnstair == 0) {
coord cc;
mazexy(&cc);
xdnstair = cc.x;
ydnstair = cc.y;
levl[cc.x][cc.y].typ = STAIRS;
}
br = Is_branchlev(&u.uz);
if (br && u.uz.dlevel == 1) {
d_level ltmp;
if (on_level(&u.uz, &br->end1))
assign_level(&ltmp, &br->end2);
else
assign_level(&ltmp, &br->end1);
switch(br->type) {
case BR_STAIR:
case BR_NO_END1:
case BR_NO_END2: /* OK to assign to sstairs if it's not used */
assign_level(&sstairs.tolev, &ltmp);
break;
case BR_PORTAL: /* max of 1 portal per level */
{
register struct trap *ttmp;
for(ttmp = ftrap; ttmp; ttmp = ttmp->ntrap)
if (ttmp->ttyp == MAGIC_PORTAL)
break;
if (!ttmp) panic("getlev: need portal but none found");
assign_level(&ttmp->dst, &ltmp);
}
break;
}
} else if (!br) {
/* Remove any dangling portals. */
register struct trap *ttmp;
for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap)
if (ttmp->ttyp == MAGIC_PORTAL) {
deltrap(ttmp);
break; /* max of 1 portal/level */
}
}
}
/* must come after all mons & objs are restored */
relink_timers(ghostly);
relink_light_sources(ghostly);
reset_oattached_mids(ghostly);
if (ghostly)
clear_id_mapping();
}
void
get_plname_from_file(fd, plbuf)
int fd;
char *plbuf;
{
int rlen, pltmpsiz = 0;
rlen = read(fd, (genericptr_t) &pltmpsiz, sizeof(pltmpsiz));
rlen = read(fd, (genericptr_t) plbuf, pltmpsiz);
return;
}
STATIC_OVL void
restore_msghistory(fd)
register int fd;
{
int msgsize, msgcount = 0;
char msg[BUFSZ];
while(1) {
mread(fd, (genericptr_t) &msgsize, sizeof(msgsize));
if(msgsize == -1) break;
if (msgsize > (BUFSZ - 1))
panic("restore_msghistory: msg too big (%d)", msgsize);
mread(fd, (genericptr_t)msg, msgsize);
msg[msgsize] = '\0';
putmsghistory(msg);
++msgcount;
}
#ifdef DEBUG_MSGCOUNT
pline("Read %d messages from savefile.", msgcount);
#endif
}
/* Clear all structures for object and monster ID mapping. */
STATIC_OVL void
clear_id_mapping()
{
struct bucket *curr;
while ((curr = id_map) != 0) {
id_map = curr->next;
free((genericptr_t) curr);
}
n_ids_mapped = 0;
}
/* Add a mapping to the ID map. */
STATIC_OVL void
add_id_mapping(gid, nid)
unsigned gid, nid;
{
int idx;
idx = n_ids_mapped % N_PER_BUCKET;
/* idx is zero on first time through, as well as when a new bucket is */
/* needed */
if (idx == 0) {
struct bucket *gnu = (struct bucket *) alloc(sizeof(struct bucket));
gnu->next = id_map;
id_map = gnu;
}
id_map->map[idx].gid = gid;
id_map->map[idx].nid = nid;
n_ids_mapped++;
}
/*
* Global routine to look up a mapping. If found, return TRUE and fill
* in the new ID value. Otherwise, return false and return -1 in the new
* ID.
*/
boolean
lookup_id_mapping(gid, nidp)
unsigned gid, *nidp;
{
int i;
struct bucket *curr;
if (n_ids_mapped)
for (curr = id_map; curr; curr = curr->next) {
/* first bucket might not be totally full */
if (curr == id_map) {
i = n_ids_mapped % N_PER_BUCKET;
if (i == 0) i = N_PER_BUCKET;
} else
i = N_PER_BUCKET;
while (--i >= 0)
if (gid == curr->map[i].gid) {
*nidp = curr->map[i].nid;
return TRUE;
}
}
return FALSE;
}
STATIC_OVL void
reset_oattached_mids(ghostly)
boolean ghostly;
{
struct obj *otmp;
unsigned oldid, nid;
for (otmp = fobj; otmp; otmp = otmp->nobj) {
if (ghostly && otmp->oattached == OATTACHED_MONST && otmp->oxlth) {
struct monst *mtmp = (struct monst *)otmp->oextra;
mtmp->m_id = 0;
mtmp->mpeaceful = mtmp->mtame = 0; /* pet's owner died! */
}
if (ghostly && otmp->oattached == OATTACHED_M_ID) {
(void) memcpy((genericptr_t)&oldid, (genericptr_t)otmp->oextra,
sizeof(oldid));
if (lookup_id_mapping(oldid, &nid))
(void) memcpy((genericptr_t)otmp->oextra, (genericptr_t)&nid,
sizeof(nid));
else
otmp->oattached = OATTACHED_NOTHING;
}
}
}
void
minit()
{
(*restoreprocs.restore_minit)();
return;
}
void
mread(fd, buf, len)
register int fd;
register genericptr_t buf;
register unsigned int len;
{
(*restoreprocs.restore_mread)(fd, buf, len);
return;
}
/* examine the version info and the savefile_info data
that immediately follows it.
Return 0 if it passed the checks.
Return 1 if it failed the version check.
Return 2 if it failed the savefile feature check.
Return -1 if it failed for some unknown reason.
*/
int
validate(fd, name)
int fd;
const char *name;
{
int rlen;
struct savefile_info sfi;
unsigned long compatible;
boolean verbose = name ? TRUE : FALSE, reslt = FALSE;
if (!(reslt = uptodate(fd, name))) return 1;
rlen = read(fd, (genericptr_t) &sfi, sizeof sfi);
minit(); /* ZEROCOMP */
if (rlen == 0) {
if (verbose) {
pline("File \"%s\" is empty during save file feature check?", name);
wait_synch();
}
return -1;
}
compatible = (sfi.sfi1 & sfcap.sfi1);
if ((sfi.sfi1 & SFI1_ZEROCOMP) == SFI1_ZEROCOMP) {
if ((compatible & SFI1_ZEROCOMP) != SFI1_ZEROCOMP) {
if (verbose) {
pline("File \"%s\" has incompatible ZEROCOMP compression.", name);
wait_synch();
}
return 2;
} else if ((sfrestinfo.sfi1 & SFI1_ZEROCOMP) != SFI1_ZEROCOMP) {
set_restpref("zerocomp");
}
}
if ((sfi.sfi1 & SFI1_EXTERNALCOMP) == SFI1_EXTERNALCOMP) {
if ((compatible & SFI1_EXTERNALCOMP) != SFI1_EXTERNALCOMP) {
if (verbose) {
pline("File \"%s\" lacks required internal compression.", name);
wait_synch();
}
return 2;
} else if ((sfrestinfo.sfi1 & SFI1_EXTERNALCOMP) != SFI1_EXTERNALCOMP) {
set_restpref("externalcomp");
}
}
/* RLECOMP check must be last, after ZEROCOMP or INTERNALCOMP adjustments */
if ((sfi.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP) {
if ((compatible & SFI1_RLECOMP) != SFI1_RLECOMP) {
if (verbose) {
pline("File \"%s\" has incompatible run-length compression.", name);
wait_synch();
}
return 2;
} else if ((sfrestinfo.sfi1 & SFI1_RLECOMP) != SFI1_RLECOMP) {
set_restpref("rlecomp");
}
}
/* savefile does not have RLECOMP level location compression, so adjust */
else set_restpref("!rlecomp");
return 0;
}
void
reset_restpref()
{
#ifdef ZEROCOMP
if (iflags.zerocomp)
set_restpref("zerocomp");
else
#endif
set_restpref("externalcomp");
#ifdef RLECOMP
if (iflags.rlecomp)
set_restpref("rlecomp");
else
#endif
set_restpref("!rlecomp");
}
void
set_restpref(suitename)
const char *suitename;
{
if (!strcmpi(suitename, "externalcomp")) {
restoreprocs.name = "externalcomp";
restoreprocs.restore_mread = def_mread;
restoreprocs.restore_minit = def_minit;
sfrestinfo.sfi1 |= SFI1_EXTERNALCOMP;
sfrestinfo.sfi1 &= ~SFI1_ZEROCOMP;
def_minit();
}
if (!strcmpi(suitename, "!rlecomp")) {
sfrestinfo.sfi1 &= ~SFI1_RLECOMP;
}
#ifdef ZEROCOMP
if (!strcmpi(suitename, "zerocomp")) {
restoreprocs.name = "zerocomp";
restoreprocs.restore_mread = zerocomp_mread;
restoreprocs.restore_minit = zerocomp_minit;
sfrestinfo.sfi1 |= SFI1_ZEROCOMP;
sfrestinfo.sfi1 &= ~SFI1_EXTERNALCOMP;
zerocomp_minit();
}
#endif
#ifdef RLECOMP
if (!strcmpi(suitename, "rlecomp")) {
sfrestinfo.sfi1 |= SFI1_RLECOMP;
}
#endif
}
#ifdef ZEROCOMP
#define RLESC '\0' /* Leading character for run of RLESC's */
#ifndef ZEROCOMP_BUFSIZ
#define ZEROCOMP_BUFSIZ BUFSZ
#endif
static NEARDATA unsigned char inbuf[ZEROCOMP_BUFSIZ];
static NEARDATA unsigned short inbufp = 0;
static NEARDATA unsigned short inbufsz = 0;
static NEARDATA short inrunlength = -1;
static NEARDATA int mreadfd;
STATIC_OVL int
zerocomp_mgetc()
{
if (inbufp >= inbufsz) {
inbufsz = read(mreadfd, (genericptr_t)inbuf, sizeof inbuf);
if (!inbufsz) {
if (inbufp > sizeof inbuf)
error("EOF on file #%d.\n", mreadfd);
inbufp = 1 + sizeof inbuf; /* exactly one warning :-) */
return -1;
}
inbufp = 0;
}
return inbuf[inbufp++];
}
STATIC_OVL void
zerocomp_minit()
{
inbufsz = 0;
inbufp = 0;
inrunlength = -1;
}
STATIC_OVL void
zerocomp_mread(fd, buf, len)
int fd;
genericptr_t buf;
register unsigned len;
{
/*register int readlen = 0;*/
if (fd < 0) error("Restore error; mread attempting to read file %d.", fd);
mreadfd = fd;
while (len--) {
if (inrunlength > 0) {
inrunlength--;
*(*((char **)&buf))++ = '\0';
} else {
register short ch = zerocomp_mgetc();
if (ch < 0) {
restoreprocs.mread_flags = -1;
return;
}
if ((*(*(char **)&buf)++ = (char)ch) == RLESC) {
inrunlength = zerocomp_mgetc();
}
}
/*readlen++;*/
}
}
#endif /* ZEROCOMP */
STATIC_OVL void
def_minit()
{
return;
}
STATIC_OVL void
def_mread(fd, buf, len)
register int fd;
register genericptr_t buf;
register unsigned int len;
{
register int rlen;
#if defined(BSD) || defined(ULTRIX)
rlen = read(fd, buf, (int) len);
if(rlen != len){
#else /* e.g. SYSV, __TURBOC__ */
rlen = read(fd, buf, (unsigned) len);
if((unsigned)rlen != len){
#endif
if (restoreprocs.mread_flags == 1) { /* means "return anyway" */
restoreprocs.mread_flags = -1;
return;
} else {
pline("Read %d instead of %u bytes.", rlen, len);
if(restoring) {
(void) close(fd);
(void) delete_savefile();
error("Error restoring old game.");
}
panic("Error reading level file.");
}
}
}
#ifndef GOLDOBJ
/*
* Takes all of the gold objects out of the invent or
* mtmp->minvent chain and puts it into either
* u.ugold or mtmp->mgold.
*/
void
put_gold_back(head_ptr, goldptr)
struct obj **head_ptr;
long *goldptr;
{
struct obj *goldobj;
long gld = 0L;
while ((goldobj = gold_in(*head_ptr))) {
gld += goldobj->quan;
extract_nobj(goldobj, head_ptr);
dealloc_obj(goldobj);
}
if (gld) *goldptr += gld;
}
STATIC_OVL struct obj *
gold_in(chn)
struct obj *chn;
{
struct obj *otmp;
for(otmp = chn; otmp; otmp = otmp->nobj)
if(otmp->otyp == GOLD_PIECE)
return(otmp);
return((struct obj *) 0);
}
#endif /*GOLDOBJ*/
/*restore.c*/