/* savelev.c	*/
#include "header.h"

#ifdef __MSDOS__

# define SWAPPING

extern unsigned long coreleft(void);

/*
 * how much memory must we leave after allocating space for all the
 *  levels? we need memory for the fortune file and for spheres.
 */

# define LEFTOVERS 20000

#endif

/*
 * When a player leaves a level, the arrays describing that level are
 *  concatenated and placed in a Saved_Level structure. Memory space for
 *  these structures is pointed to by saved_levels[]. For each entry in
 *  saved_levels[], the corresponding entry in slot_in_use[] is 1 if
 *  that entry is currently holding a level, -1 if that level couldn't
 *  be malloc'd, and 0 if it is free. For each level, its entry in
 *  level_locs[] is the index into saved_levels[] where it is stored, or
 *  -1 if it isn't stored there (it is the current level, or it hasn't
 *  been visited yet). If the level is stored in the swapfile, level_loc
 *  is (-2 - swap_slot). slot_time holds the time when the level
 *  in that slot was saved, for implementing LRU swapping. An entry in
 *  swap_slot_in_use is 1 if the slot is in use, 0 if it is free, and -1 if
 *  it hasn't been allocated yet. If swap_slot_in_use[0] == -1, the swapfile
 *  hasn't been opened yet.
 */

typedef char Char_Ary[MAXX][MAXY];
typedef short Short_Ary[MAXX][MAXY];

/* this is the structure that holds the entire dungeon specifications */
typedef struct {
  Short_Ary hitp;
  Char_Ary  mitem;
  Char_Ary  item;
  Short_Ary iarg;
  Char_Ary  know;
} Saved_Level;

#define NLEVELS (MAXLEVEL + MAXVLEVEL)

static Saved_Level *saved_levels[NLEVELS];

#ifdef SWAPPING

static int slot_in_use[NLEVELS], level_locs[NLEVELS];
static long slot_time[NLEVELS];
static int swap_slot_in_use[NLEVELS];
static char swap_file_name[] = "lrnswpxxxxxx";
static FILE *swap_fp = NULL;

/*
 * open and close the swap file...
 */

close_swap_file()
{
  fclose(swap_fp);
  unlink(swap_file_name);
}

open_swap_file()
{
  mktemp(swap_file_name);
  if ((swap_fp = fopen(swap_file_name, "w+b")) == NULL) {
    perror("Can't open swap file");
    abort();
  }
  lprintf("(Opened swap file)\n");

  atexit(close_swap_file);
}

#define SWAP_SEEK(slot) fseek(swap_fp, (long)(slot)*sizeof(Saved_Level), 0)

/*
 * swap_write: write a level out to the swap file
 */

swap_write(storage, slot)
Saved_Level *storage;
int slot;
{
  if (swap_fp == NULL || slot < 0 || slot >= NLEVELS || storage == NULL) {
    puts("swap_write: bogus arguments");
    abort();
  }

  if (SWAP_SEEK(slot) ||
       fwrite((char *)storage, sizeof(Saved_Level), 1, swap_fp) != 1) {
    perror("Can't write to swap file");
    abort();
  }
}

/*
 * swap_read: read a level in from the swap file
 */

swap_read(storage, slot)
Saved_Level *storage;
int slot;
{
  if (swap_fp == NULL || slot < 0 || slot >= NLEVELS || storage == NULL) {
    puts("swap_read: bogus arguments");
    abort();
  }

  if (SWAP_SEEK(slot) ||
       fread((char *)storage, sizeof(Saved_Level), 1, swap_fp) != 1) {
    perror("Can't read from swap file");
    abort();
  }
}

/*
 * swap_out_level: swap the least-recently saved level out.
 */

swap_out_level()
{
  int lru_slot = -1;
  long lru_time;
  int swap_slot = -1;
  int i, level;

  time(&lru_time);

  for (i=0; i<NLEVELS; i++)
    if (slot_in_use[i] == 1 && slot_time[i] < lru_time) {
      lru_time = slot_time[i];
      lru_slot = i;
    }

  if (lru_slot == -1) {
    puts("swap_out_level: couldn't find a level to swap out!");
    abort();
  }

  for (i=0; i<NLEVELS; i++)
    if (swap_slot_in_use[i] != 1) {
      if (swap_slot_in_use[i] == -1 && i == 0) open_swap_file();
      swap_slot = i;
      break;
    }

  if (swap_slot == -1) {
    puts("swap_out_level: couldn't find a free swap slot!");
    abort();
  }

  swap_write(saved_levels[lru_slot], swap_slot);

  level=-1;
  for (i=0; i<NLEVELS; i++)
    if (level_locs[i] == lru_slot) {
      level = i;
      break;
    }

  if (level == -1) {
    puts("swap_out_level: i couldn't figure out what level i was outswapping");
    abort();
  }

  level_locs[level] = -2-swap_slot;
  slot_in_use[lru_slot] = 0;
  swap_slot_in_use[swap_slot] = 1;
}

/*
 * alloc_level allocates a slot for a new level and returns a pointer to the
 *  Saved_Level structure. note: this pointer may be invalidated by any
 *  future calls to get_level or alloc_level.
 */

static Saved_Level *alloc_level(level)
int level;
{
  int i;
  int tried_to_swap = 0;

try_again:
  for (i=0; i<NLEVELS; i++)
    if (slot_in_use[i] == 0) {
      slot_in_use[i] = 1;
      level_locs[level] = i;
      time(&slot_time[i]);
      if (saved_levels[i] == NULL) {
        printf("bug in alloc_level!");
        abort();
      }
      return saved_levels[i];
    }

  if (tried_to_swap) {
    puts("Can't find a place to put a level!");
    abort();
  }
  else {
    tried_to_swap = 1;
    swap_out_level();
    goto try_again;
  }
}

/*
 * get_level returns a pointer to the Saved_Level structure for the indicated
 *  level. note: this pointer may be invalidated by any future calls to
 *  get_level or alloc_level.
 */

static Saved_Level *get_level(level)
int level;
{
  int slot = level_locs[level];
  Saved_Level *storage = saved_levels[slot];

  if (slot == -1 || slot >= NLEVELS || slot <= -2 - NLEVELS) {
    puts("bug in get_level!");
    abort();
  }

  if (slot >= 0)
    storage = saved_levels[slot];
  else {

/* swap in the level */

    slot = -2 - slot;
    if (swap_slot_in_use[slot] != 1) {
      puts("bogus swapped level_loc in get_level!");
      abort();
    }
    storage = alloc_level(level);

    swap_read(storage, slot);

    swap_slot_in_use[slot] = 0;
  }

  if (storage == NULL) {
    puts("bogus storage pointer in get_level!");
    abort();
  }
  return storage;
}

/*
 * free the storage for a level
 */

static free_level(level)
int level;
{
  int slot = level_locs[level];

  if (slot < -2-NLEVELS || slot == -1 || slot >= NLEVELS) {
    printf("bug in free_level!\n");
    abort();
  }

  if (slot >= 0)
    slot_in_use[slot] = 0;
  else
    swap_slot_in_use[-2-slot] = 0;

  level_locs[level] = -1;
}

#endif /* SWAPPING */

/*
 *	routine to save the present level into storage
 */
savelevel()
{
#ifdef SWAPPING
  Saved_Level *storage = alloc_level(level);
#else
  Saved_Level *storage = saved_levels[level];
#endif

  memcpy(storage->hitp,  hitp,  sizeof(Short_Ary));
  memcpy(storage->mitem, mitem, sizeof(Char_Ary));
  memcpy(storage->item,  item,  sizeof(Char_Ary));
  memcpy(storage->iarg,  iarg,  sizeof(Short_Ary));
  memcpy(storage->know,  know,  sizeof(Char_Ary));
}

/*
 *	routine to restore a level from storage
 */
getlevel()
{
#ifdef SWAPPING
  Saved_Level *storage = get_level(level);
#else
  Saved_Level *storage = saved_levels[level];
#endif

  memcpy(hitp,  storage->hitp,  sizeof(Short_Ary));
  memcpy(mitem, storage->mitem, sizeof(Char_Ary));
  memcpy(item,  storage->item,  sizeof(Char_Ary));
  memcpy(iarg,  storage->iarg,  sizeof(Short_Ary));
  memcpy(know,  storage->know,  sizeof(Char_Ary));

#ifdef SWAPPING
  free_level(level);
#endif
}

/*
	to save the game in a file
 */
static long int zzz=0;
static char courses_taken[25]; /* must be longer than courses[*].taken in store.c */
extern int rmst;
extern long lasttime;
savegame(fname)
char *fname;
{
	register int i,k;
	register struct sphere *sp;
	struct stat statbuf;
        Saved_Level *storage;

	nosignal=1;  
	lflush();	
	savelevel();
	ointerest();
	if (lcreat(fname, 1) < 0) {
	  lcreat((char*)0); 
	  lprintf("\nCan't open file <%s> to save game\n",fname);
	  nosignal=0;  
	  return(-1);
	}

	set_score_output();
	lwrite((char*)beenhere,MAXLEVEL+MAXVLEVEL);
	for (k=0; k<MAXLEVEL+MAXVLEVEL; k++)
	  if (beenhere[k]) {
#ifdef SWAPPING
	    storage = get_level(k);
#else
	    storage = saved_levels[k];
#endif
	    lwrite((char*)storage, sizeof(Saved_Level));
	  }
	lwrite((char*)&c[0],100*sizeof(long));
	lprint((long)gtime);		
	lprc(level);
	lprc(playerx);		
	lprc(playery);
	lwrite((char*)iven, NINVT);	
	lwrite((char*)ivenarg, NINVT*sizeof(short));
	lwrite((char*)char_class,20);	
	for (k=0; k<MAXSCROLL; k++)  lprc(scrollname[k][0]);
	for (k=0; k<MAXPOTION; k++)  lprc(potionname[k][0]);
	lwrite((char*)spelknow,SPNUM);		 
	lprc(wizard);
	lprc(rmst);		/*	random monster generation counter */
	for (i=0; i<90; i++)	lprc(itm[i].qty);
	(void) get_taken( N_ELEM(courses_taken), courses_taken);
	lwrite((char*)courses_taken, N_ELEM(courses_taken));			
	lprc(cheat);		

	/* genocide info */
	for (i=0; i<MAXMONST; i++) lprc(monster[i].genocided);

	/* save spheres of annihilation */
	for (sp=spheres; sp; sp=sp->p)
	  lwrite((char*)sp,sizeof(struct sphere));	

	time(&zzz);			
	lprint((long)(zzz-initialtime));
	lwrite((char*)&zzz,sizeof(long));

	if (fstat(lfd,&statbuf)< 0) lprint(0L);
	else 
	  lprint((long)statbuf.st_ino); /* inode # */

	lwclose();	
	lastmonst[0] = 0;
	lcreat((char*)0);  
	nosignal=0;
	return(0);
}

restoregame(fname)
char *fname;
{
	register int i,k;
	register struct sphere *sp,*sp2;
	struct stat filetimes;
        Saved_Level *storage;


	cursors(); 
	lprcat("\nRestoring . . .");  
	lflush();
	if (lopen(fname, 1) <= 0) {
	  lcreat((char*)0); 
	  lprintf("\nCan't open file <%s> to restore game\n",fname);
	  nap(2000); 
	  c[GOLD]=c[BANKACCOUNT]=0;  
	  died(-265); 
	  return;
	}

	lrfill((char*)beenhere,MAXLEVEL+MAXVLEVEL);
	for (k=0; k<MAXLEVEL+MAXVLEVEL; k++)
	  if (beenhere[k]) {
#ifdef SWAPPING
	    storage = alloc_level(k);
#else
	    storage = saved_levels[k];
#endif
	    lrfill((char*)storage, sizeof(Saved_Level));
	  }

	lrfill((char*)&c[0],100*sizeof(long));	
	gtime = lrint();
	level = c[CAVELEVEL] = lgetc();

	playerx = lgetc();		
	playery = lgetc();

	lrfill((char*)iven, NINVT);
	lrfill((char*)ivenarg, NINVT*sizeof(short));
	lrfill((char*)char_class,20);		

	for (k=0; k<MAXSCROLL; k++)  scrollname[k][0] = lgetc();
	for (k=0; k<MAXPOTION; k++)  potionname[k][0] = lgetc();

	lrfill((char*)spelknow,SPNUM);		

	wizard = lgetc();
	rmst = lgetc();			/*	random monster creation flag */

	for (i=0; i<90; i++)	
	  itm[i].qty = lgetc();

	lrfill((char*)courses_taken, N_ELEM(courses_taken));			
	(void) rest_taken( N_ELEM(courses_taken), courses_taken);
	cheat = lgetc();

	/* genocide info */
	for (i=0; i<MAXMONST; i++) monster[i].genocided=lgetc(); 

	for (sp=0,i=0; i<c[SPHCAST]; i++) {
	  sp2 = sp;
	  sp = (struct sphere *)malloc(sizeof(struct sphere));
	  if (sp==0) { 
	    fprintf(stderr, "Can't malloc() for sphere space\n"); 
	    break; 
	  }
	  /* get spheres of annihilation */
	  lrfill((char*)sp,sizeof(struct sphere));	
	  sp->p=0;	/* null out pointer */
	  if (i==0) spheres=sp;	/* beginning of list */
	  else sp2->p = sp;
	}


	time(&zzz);
	initialtime = zzz-lrint();

	fstat(fd,&filetimes);/*get the creation and modification time of file*/

	lrfill((char*)&zzz,sizeof(long));	

	zzz += 6;
#ifndef __TURBOC__
	if (filetimes.st_ctime > zzz) 
	  fsorry();	/*file create time	*/
	else if (filetimes.st_mtime > zzz) 
	  fsorry(); /*	file modify time*/
#endif


	if (c[HP]<0) { 
	  died(284); 
	  return; 
	}	/* died a post mortem death */

	oldx = oldy = 0;

	i = lrint();  /* inode # */
#ifndef __TURBOC__
	if (i && (filetimes.st_ino!=i)) 
	  fsorry();	/* different inode number, file was copied */
#endif

	lrclose();

	if (strcmp(fname,ckpfile) == 0) {
	  if (lappend(fname, 1) < 0) 
	    fcheat();  
	  else { 
	    lprc(' '); 
	    lwclose(); 
	  }
	  lcreat((char*)0);
	}
	else if (unlink(fname) < 0) 
	  fcheat(); /* can't unlink save file */

	/*	for the greedy cheater checker	*/
	/* for (k=0; k<6; k++) if (c[k]>99) greedy();
	if (c[HPMAX]>999 || c[SPELLMAX]>125) greedy(); */
	if (c[LEVEL]==25 && c[EXPERIENCE]>skill[24]) {
	  long tmp = c[EXPERIENCE]-skill[24]; /* amount to go up */
	  c[EXPERIENCE] = skill[24];
	  raiseexperience((long)tmp);
	}
	getlevel();  
	lasttime=gtime;

	if (has_object(OLARNEYE) >= 0 ) {
	  monstnamelist[DEMONLORD]   = '1';
	  monstnamelist[DEMONLORD+1] = '2';
	  monstnamelist[DEMONLORD+2] = '3';
	  monstnamelist[DEMONLORD+3] = '4';
	  monstnamelist[DEMONLORD+4] = '5';
	  monstnamelist[DEMONLORD+5] = '6';
	  monstnamelist[DEMONLORD+6] = '7';
	  monstnamelist[DEMONPRINCE] = '9';
	  monstnamelist[LUCIFER] = '0';
	}
}

/*
	subroutine to not allow greedy cheaters
 */
greedy()
{
	if (wizard) return;

lprcat("\n\nI am so sorry, but your character is a little TOO good!  Since this\n");
lprcat("cannot normally happen from an honest game, I must assume that you cheated.\n");
lprcat("In that you are GREEDY as well as a CHEATER, I cannot allow this game\n");
	lprcat("to continue.\n"); 
	nap(5000);  
	c[GOLD]=c[BANKACCOUNT]=0;  
	died(-267); 
	return;
}

/*
	subroutine to not allow altered save files and terminate the attempted
	restart
 */
fsorry()
{
	if(cheat) return;
lprcat("\nSorry, but your savefile has been altered.\n");
lprcat("However, seeing as I am a good sport, I will let you play.\n");
lprcat("Be advised though, you won't be placed on the scoreboard.");
	cheat = 1;	
	nap(4000);
}

/*
	subroutine to not allow game if save file can't be deleted
 */
fcheat()
{
	if (wizard) return;
	if(cheat) return;

lprcat("\nSorry, but your savefile can't be deleted.  This can only mean\n");
lprcat("that you tried to CHEAT by protecting the directory the savefile\n");
lprcat("is in.  Since this is unfair to the rest of the Ularn community, I\n");
lprcat("cannot let you play this game.\n");
	nap(5000);  
	c[GOLD]=c[BANKACCOUNT]=0;  
	died(-268); 
	return;
}

init_cells()
{
  int i, missing=0;

  for (i=0; i<NLEVELS; i++) {
#ifdef __MSDOS__
    if (coreleft() <= LEFTOVERS)
      saved_levels[i] = NULL;
    else
#endif
    saved_levels[i] = (Saved_Level *)malloc(sizeof(Saved_Level));
#ifdef SWAPPING
    if (saved_levels[i] == NULL) {
      ++missing;
      slot_in_use[i] = -1;
    }
    else slot_in_use[i] = 0;

    level_locs[i] = -1;
    slot_time[i] = 0;
    swap_slot_in_use[i] = -1;
#else
    if (saved_levels[i] == NULL) died(-285);
#endif /* SWAPPING */
  }

/* must have at least one save area available */

#ifdef SWAPPING
  if (saved_levels[0] == NULL) died(-285);

  if (missing) {
    printf("Couldn't allocate space for %d levels\n", missing);
    nap(2000);
  }
#endif
}


