/* source/death.c: code executed when player dies

   Copyright (C) 1989-2008 James E. Wilson, Robert A. Koeneke, 
                           David J. Grabiner

   This file is part of Umoria.

   Umoria is free software; you can redistribute it and/or modify 
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Umoria is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License 
   along with Umoria.  If not, see <http://www.gnu.org/licenses/>. */

/* Must read this before externs.h, as some global declarations use FILE. */
#include <stdio.h>

#include "config.h"
#include "constant.h"
#include "types.h"

#ifdef Pyramid
#include <sys/time.h>
#else
#include <time.h>
#endif

#include <ctype.h>

#ifndef USG
/* only needed for Berkeley UNIX */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#else
#ifdef SYS_V /* XENIX and SYSV seem to need this */
#include <sys/types.h>
#endif
#endif

#ifdef MSDOS
#include <io.h>
#else
#if !defined(ATARIST_MWC) && !defined(MAC) && !defined(AMIGA)
#if !defined(ATARIST_TC)
#ifndef VMS
#include <pwd.h>
#else
#include <file.h>
#endif
#endif
#endif
#endif

#ifdef VMS
unsigned int getuid(), getgid();
#else
#ifdef unix
#ifdef USG
unsigned short getuid(), getgid();
#else
#ifndef SECURE
#ifdef BSD4_3
uid_t getuid(), getgid();
#else  /* other BSD versions */
int getuid(), getgid();
#endif
#endif
#endif
#endif
#endif

#ifdef USG
#ifndef ATARIST_MWC
#include <string.h>
#ifndef VMS
#ifndef ATARIST_TC
#include <fcntl.h>
#endif
#endif
#endif
#else
#include <strings.h>
#endif

/* This must be included after fcntl.h, which has a prototype for `open'
   on some systems.  Otherwise, the `open' prototype conflicts with the
   `topen' declaration.  */
#include "externs.h"

#ifndef BSD4_3
#ifndef ATARIST_TC
long lseek();
#endif /* ATARTIST_TC */
#else
off_t lseek();
#endif

#if defined(USG) || defined(VMS) || defined(atarist)
#ifndef L_SET
#define L_SET 0
#endif
#ifndef L_INCR
#define L_INCR 1
#endif
#endif

#ifndef VMS
#ifndef MAC
#if defined(ultrix) || defined(USG)
void exit ();
#endif
#endif
#endif

#if defined(LINT_ARGS)
static void date(char *);
static char *center_string(char *, char *);
static void print_tomb(void);
static void kingly(void);
#endif

#ifdef ATARIST_TC
/* Include this to get prototypes for standard library functions.  */
#include <stdlib.h>
#endif

#ifndef VMS
#ifndef MAC
#if !defined(ATARIST_MWC) && !defined(AMIGA)
long time();
#endif
#endif
#endif

static void date(day)
char *day;
{
  register char *tmp;
#ifdef MAC
  time_t clockvar;
#else
  long clockvar;
#endif

#ifdef MAC
  clockvar = time((time_t *) 0);
#else
  clockvar = time((long *) 0);
#endif
  tmp = ctime(&clockvar);
  tmp[10] = '\0';
  (void) strcpy(day, tmp);
}

/* Centers a string within a 31 character string		-JWT-	 */
static char *center_string(centered_str, in_str)
char *centered_str;
char *in_str;
{
  register int i, j;

  i = strlen(in_str);
  j = 15 - i/2;
  (void) sprintf (centered_str, "%*s%s%*s", j, "", in_str, 31 - i - j, "");
  return centered_str;
}


#ifndef __TURBOC__
#if (defined(USG) || defined(atarist) || defined(HPUX)) && !defined(VMS)
#if !defined(AMIGA) && !defined(MAC) && !defined(ATARIST_TC)

#include <sys/stat.h>
#include <errno.h>

/* The following code is provided especially for systems which		-CJS-
   have no flock system call. It has never been tested.		*/

/* DEBIAN_LINUX defined because fcntlbits.h defines EX and SH the       -RJW-
 * other way.  The comment below indicates that they're not
 * distinguished anyways, so this should be harmless, and this does
 * seem to be the prevailing order (c.f. IRIX 6.5) but just in case,
 * they've been ifdef'ed. */

#ifdef DEBIAN_LINUX
#define LOCK_SH 1
#define LOCK_EX 2
#else /* DEBIAN_LINUX */
#define LOCK_EX	1
#define LOCK_SH	2
#endif /* DEBIAN_LINUX */
#define LOCK_NB	4
#define LOCK_UN	8

/* An flock HACK.  LOCK_SH and LOCK_EX are not distinguished.  DO NOT release
   a lock which you failed to set!  ALWAYS release a lock you set! */
static int flock(f, l)
int f, l;
{
  struct stat sbuf;
  char lockname[80];

  if (fstat (f, &sbuf) < 0)
    return -1;
#ifdef atarist
  (void) sprintf (lockname, (char *)prefix_file((char *)"moria.%d"),
		  sbuf.st_ino);
#else
#ifdef __linux__
  (void) sprintf (lockname, "/tmp/moria.%ld", sbuf.st_ino);
#else
  (void) sprintf (lockname, "/tmp/moria.%d", sbuf.st_ino);
#endif
#endif
  if (l & LOCK_UN)
    return unlink(lockname);

  while (open (lockname, O_WRONLY|O_CREAT|O_EXCL, 0644) < 0)
    {
      if (errno != EEXIST)
	return -1;
      if (stat(lockname, &sbuf) < 0)
	return -1;
      /* Locks which last more than 10 seconds get deleted. */
      if (time((long *)0) - sbuf.st_mtime > 10)
	{
	  if (unlink(lockname) < 0)
	    return -1;
	}
      else if (l & LOCK_NB)
	return -1;
      else
	(void) sleep(1);
    }
  return 0;
}
#endif
#endif
#endif

void display_scores(show_player)
int show_player;
{
  register int i, rank;
  high_scores score;
  char input;
  char string[100];
  int8u version_maj, version_min, patch_level;
#if defined(unix) || defined(VMS)
  int16 player_uid;
#endif

#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
#if defined(MAC) || defined(MSDOS)
  if ((highscore_fp = fopen(MORIA_TOP, "rb")) == NULL)
#else
  if ((highscore_fp = fopen(MORIA_TOP, "r")) == NULL)
#endif
    {
      (void) sprintf (string, "Error opening score file \"%s\"\n", MORIA_TOP);
      msg_print(string);
      msg_print(CNIL);
      return;
    }
#endif

#ifndef BSD4_3
  (void) fseek(highscore_fp, (long)0, L_SET);
#else
  (void) fseek(highscore_fp, (off_t)0, L_SET);
#endif

  /* Read version numbers from the score file, and check for validity.  */
  version_maj = getc (highscore_fp);
  version_min = getc (highscore_fp);
  patch_level = getc (highscore_fp);
  /* Support score files from 5.2.2 to present.  */
  if (feof (highscore_fp))
    /* An empty score file. */
    ;
  else if ((version_maj != CUR_VERSION_MAJ)
	   || (version_min > CUR_VERSION_MIN)
	   || (version_min == CUR_VERSION_MIN && patch_level > PATCH_LEVEL)
	   || (version_min == 2 && patch_level < 2)
	   || (version_min < 2))
    {
      msg_print("Sorry. This scorefile is from a different version of \
umoria.");
      msg_print (CNIL);
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC)
      (void) fclose (highscore_fp);
#endif
      return;
    }

#ifdef unix
  player_uid = getuid ();
#else
#ifdef VMS
  player_uid = (getgid()*1000) + getuid();
#else
  /* Otherwise player_uid is not used.  */
#endif
#endif

  /* set the static fileptr in save.c to the highscore file pointer */
  set_fileptr(highscore_fp);

  rank = 1;
  rd_highscore(&score);
  while (!feof(highscore_fp))
    {
      i = 1;
      clear_screen();
      /* Put twenty scores on each page, on lines 2 through 21. */
      while (!feof(highscore_fp) && i < 21)
	{
	  /* Only show the entry if show_player false, or if the entry
	     belongs to the current player.  */
	  if (! show_player ||
#if defined(unix) || defined(VMS)
	      score.uid == player_uid
#else
	      /* Assume microcomputers should always show every entry. */
	      TRUE
#endif
	      )
	    {
	      (void) sprintf(string,
			   "%-4d%8ld %-19.19s %c %-10.10s %-7.7s%3d %-22.22s",
			     rank, score.points, score.name, score.sex,
			     race[score.race].trace, class[score.class].title,
			     score.lev, score.died_from);
	      prt(string, ++i, 0);
	    }
	  rank++;
	  rd_highscore(&score);
	}
      prt("Rank  Points Name              Sex Race       Class  Lvl Killed By"
	  , 0, 0);
      erase_line (1, 0);
      prt("[Press any key to continue.]", 23, 23);
      input = inkey();
      if (input == ESCAPE)
	break;
    }
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
  (void) fclose (highscore_fp);
#endif
}


int duplicate_character ()
{
  /* Only check for duplicate characters under unix and VMS.  */
#if !defined (unix) && !defined(VMS)
  return FALSE;

#else /* ! unix && ! VMS */

  high_scores score;
  int8u version_maj, version_min, patch_level;
  int16 player_uid;
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
  char string[80];
#endif

#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
#if defined(MAC) || defined(MSDOS)
  if ((highscore_fp = fopen(MORIA_TOP, "rb")) == NULL)
#else
  if ((highscore_fp = fopen(MORIA_TOP, "r")) == NULL)
#endif
    {
      (void) sprintf (string, "Error opening score file \"%s\"\n", MORIA_TOP);
      msg_print(string);
      msg_print(CNIL);
      return FALSE;
    }
#endif

#ifndef BSD4_3
  (void) fseek(highscore_fp, (long)0, L_SET);
#else
  (void) fseek(highscore_fp, (off_t)0, L_SET);
#endif

  /* Read version numbers from the score file, and check for validity.  */
  version_maj = getc (highscore_fp);
  version_min = getc (highscore_fp);
  patch_level = getc (highscore_fp);
  /* Support score files from 5.2.2 to present.  */
  if (feof (highscore_fp))
    /* An empty score file.  */
    return FALSE;
  if ((version_maj != CUR_VERSION_MAJ)
      || (version_min > CUR_VERSION_MIN)
      || (version_min == CUR_VERSION_MIN && patch_level > PATCH_LEVEL)
      || (version_min == 2 && patch_level < 2)
      || (version_min < 2))
    {
      msg_print("Sorry. This scorefile is from a different version of \
umoria.");
      msg_print (CNIL);
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
      (void) fclose (highscore_fp);
#endif
      return FALSE;
    }

  /* set the static fileptr in save.c to the highscore file pointer */
  set_fileptr(highscore_fp);

#ifdef unix
  player_uid = getuid ();
#else
#ifdef VMS
  player_uid = (getgid()*1000) + getuid();
#else
  player_uid = 0;
#endif
#endif

  rd_highscore(&score);
  while (!feof(highscore_fp))
    {
      if (score.uid == player_uid && score.birth_date == birth_date
	  && score.class == py.misc.pclass && score.race == py.misc.prace
	  && score.sex == (py.misc.male ? 'M' : 'F')
	  && strcmp (score.died_from, "(saved)"))
	return TRUE;

      rd_highscore(&score);
    }
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC)
  (void) fclose (highscore_fp);
#endif

  return FALSE;
#endif  /* ! unix && ! VMS */
}



/* Prints the gravestone of the character		-RAK-	 */
static void print_tomb()
{
  vtype str, tmp_str;
  register int i;
  char day[11];
  register char *p;
#ifdef MAC
  char func;
  int ok;
#endif

  clear_screen();
  put_buffer ("_______________________", 1, 15);
  put_buffer ("/", 2, 14);
  put_buffer ("\\         ___", 2, 38);
  put_buffer ("/", 3, 13);
  put_buffer ("\\ ___   /   \\      ___", 3, 39);
  put_buffer ("/            RIP            \\   \\  :   :     /   \\", 4, 12);
  put_buffer ("/", 5, 11);
  put_buffer ("\\  : _;,,,;_    :   :", 5, 41);
  (void) sprintf (str, "/%s\\,;_          _;,,,;_",
		  center_string (tmp_str, py.misc.name));
  put_buffer (str, 6, 10);
  put_buffer ("|               the               |   ___", 7, 9);
  if (!total_winner)
    p = title_string ();
  else
    p = "Magnificent";
  (void) sprintf (str, "| %s |  /   \\", center_string (tmp_str, p));
  put_buffer (str, 8, 9);
  put_buffer ("|", 9, 9);
  put_buffer ("|  :   :", 9, 43);
  if (!total_winner)
    p = class[py.misc.pclass].title;
  else if (py.misc.male)
    p = "*King*";
  else
    p = "*Queen*";
  (void) sprintf(str,"| %s | _;,,,;_   ____", center_string (tmp_str, p));
  put_buffer (str, 10, 9);
  (void) sprintf (str, "Level : %d", (int) py.misc.lev);
  (void) sprintf (str,"| %s |          /    \\",
		  center_string (tmp_str, str));
  put_buffer (str, 11, 9);
  (void) sprintf(str, "%ld Exp", py.misc.exp);
  (void) sprintf(str,"| %s |          :    :", center_string (tmp_str, str));
  put_buffer (str, 12, 9);
  (void) sprintf(str, "%ld Au", py.misc.au);
  (void) sprintf(str,"| %s |          :    :", center_string (tmp_str, str));
  put_buffer (str, 13, 9);
  (void) sprintf(str, "Died on Level : %d", dun_level);
  (void) sprintf(str,"| %s |         _;,,,,;_", center_string (tmp_str, str));
  put_buffer (str, 14, 9);
  put_buffer ("|            killed by            |", 15, 9);
  p = died_from;
  i = strlen (p);
  p[i] = '.';  /* add a trailing period */
  p[i+1] = '\0';
  (void) sprintf(str, "| %s |", center_string (tmp_str, p));
  put_buffer (str, 16, 9);
  p[i] = '\0';	 /* strip off the period */
  date(day);
  (void) sprintf(str, "| %s |", center_string (tmp_str, day));
  put_buffer (str, 17, 9);
  put_buffer ("*|   *     *     *    *   *     *  | *", 18, 8);
  put_buffer ("________)/\\\\_)_/___(\\/___(//_\\)/_\\//__\\\\(/_|_)_______",
	      19, 0);

 retry:
  flush();
#ifdef MAC
  /* On Mac, file_character() gets file name via std file dialog */
  /* So, the prompt for character record cannot be made to do double duty */
  put_buffer ("('F' - Save record in file / 'Y' - Display record on screen \
/ 'N' - Abort)", 23, 0);
  put_buffer ("Character record [F/Y/N]?", 22, 0);
  do
    {
      func = inkey();
      switch (func)
	{
	case 'f': case 'F':
	  func = 'F';
	  ok = TRUE;
	  break;
	case 'y': case 'Y':
	  func = 'Y';
	  ok = TRUE;
	  break;
	case 'n': case 'N':
	  func = 'N';
	  ok = TRUE;
	  break;
	default:
	  bell();
	  ok = FALSE;
	  break;
	}
    }
  while (!ok);
  if (func != 'N')
#else
  put_buffer ("(ESC to abort, return to print on screen, or file name)",
	      23, 0);
  put_buffer ("Character record?", 22, 0);
  if (get_string (str, 22, 18, 60))
#endif
    {
      for (i = 0; i < INVEN_ARRAY_SIZE; i++)
	{
	  known1(&inventory[i]);
	  known2(&inventory[i]);
	}
      calc_bonuses ();
#ifdef MAC
      if (func == 'F')
	{
	  if (!file_character())
	    goto retry;
	}
#else
      if (str[0])
	{
	  if (!file_character (str))
	    goto retry;
	}
#endif
      else
	{
	  clear_screen ();
	  display_char ();
	  put_buffer ("Type ESC to skip the inventory:", 23, 0);
	  if (inkey() != ESCAPE)
	    {
	      clear_screen ();
	      msg_print ("You are using:");
	      (void) show_equip (TRUE, 0);
	      msg_print (CNIL);
	      msg_print ("You are carrying:");
	      clear_from (1);
	      (void) show_inven (0, inven_ctr-1, TRUE, 0, CNIL);
	      msg_print (CNIL);
	    }
	}
    }
}


/* Calculates the total number of points earned		-JWT-	 */
int32 total_points()
{
  int32 total;
  int i;

  total = py.misc.max_exp + (100 * py.misc.max_dlv);
  total += py.misc.au / 100;
  for (i = 0; i < INVEN_ARRAY_SIZE; i++)
    total += item_value(&inventory[i]);
  total += dun_level*50;

  /* Don't ever let the score decrease from one save to the next.  */
  if (max_score > total)
    return max_score;

  return total;
}


/* Enters a players name on the top twenty list		-JWT-	 */
static void highscores()
{
  high_scores old_entry, new_entry, entry;
  int i;
  char *tmp;
  int8u version_maj, version_min, patch_level;
  long curpos;
#if defined(VMS) || defined(MSDOS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
  char string[100];
#endif

  clear_screen();

  if (noscore)
    return;

  if (panic_save == 1)
    {
      msg_print("Sorry, scores for games restored from panic save files \
are not saved.");
      return;
    }

  new_entry.points = total_points();
  new_entry.birth_date = birth_date;
#ifdef unix
  new_entry.uid = getuid();
#else
#ifdef VMS
  new_entry.uid = (getgid()*1000) + getuid();
#else
  new_entry.uid = 0;
#endif
#endif
  new_entry.mhp = py.misc.mhp;
  new_entry.chp = py.misc.chp;
  new_entry.dun_level = dun_level;
  new_entry.lev = py.misc.lev;
  new_entry.max_dlv = py.misc.max_dlv;
  new_entry.sex = (py.misc.male ? 'M' : 'F');
  new_entry.race = py.misc.prace;
  new_entry.class = py.misc.pclass;
  (void) strcpy(new_entry.name, py.misc.name);
  tmp = died_from;
  if ('a' == *tmp)
    {
      if ('n' == *(++tmp))
	{
	  tmp++;
	}
      while (isspace(*tmp))
	{
	  tmp++;
	}
    }
  (void) strcpy(new_entry.died_from, tmp);

  /*  First, get a lock on the high score file so no-one else tries */
  /*  to write to it while we are using it, on VMS and IBMPCs only one
      process can have the file open at a time, so we just open it here */
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC)
#if defined(MAC) || defined(MSDOS)
  if ((highscore_fp = fopen(MORIA_TOP, "rb+")) == NULL)
#else
  if ((highscore_fp = fopen(MORIA_TOP, "r+")) == NULL)
#endif
    {
      (void) sprintf (string, "Error opening score file \"%s\"\n", MORIA_TOP);
      msg_print(string);
      msg_print(CNIL);
      return;
    }
#else
#ifdef ATARIST_TC
  /* 'lock' always succeeds on the Atari ST */
#else
  if (0 != flock((int)fileno(highscore_fp), LOCK_EX))
    {
      msg_print("Error gaining lock for score file");
      msg_print(CNIL);
      return;
    }
#endif
#endif

  /* Search file to find where to insert this character, if uid != 0 and
     find same uid/sex/race/class combo then exit without saving this score */
  /* Seek to the beginning of the file just to be safe. */
#ifndef BSD4_3
  (void) fseek(highscore_fp, (long)0, L_SET);
#else
  (void) fseek(highscore_fp, (off_t)0, L_SET);
#endif

  /* Read version numbers from the score file, and check for validity.  */
  version_maj = getc (highscore_fp);
  version_min = getc (highscore_fp);
  patch_level = getc (highscore_fp);
  /* If this is a new scorefile, it should be empty.  Write the current
     version numbers to the score file.  */
  if (feof (highscore_fp))
    {
      /* Seek to the beginning of the file just to be safe. */
#ifndef BSD4_3
      (void) fseek(highscore_fp, (long)0, L_SET);
#else
      (void) fseek(highscore_fp, (off_t)0, L_SET);
#endif

      (void) putc (CUR_VERSION_MAJ, highscore_fp);
      (void) putc (CUR_VERSION_MIN, highscore_fp);
      (void) putc (PATCH_LEVEL, highscore_fp);

      /* must fseek() before can change read/write mode */
#ifndef BSD4_3
#ifdef ATARIST_TC
      /* no fseek relative to current position allowed */
      (void) fseek (highscore_fp, (long)ftell (highscore_fp), L_SET);
#else
      (void) fseek(highscore_fp, (long)0, L_INCR);
#endif
#else
      (void) fseek(highscore_fp, (off_t)0, L_INCR);
#endif
    }
  /* Support score files from 5.2.2 to present.  */
  else if ((version_maj != CUR_VERSION_MAJ)
	   || (version_min > CUR_VERSION_MIN)
	   || (version_min == CUR_VERSION_MIN && patch_level > PATCH_LEVEL)
	   || (version_min == 2 && patch_level < 2)
	   || (version_min < 2))
    {
      /* No need to print a message, a subsequent call to display_scores()
	 will print a message.  */
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC)
      (void) fclose (highscore_fp);
#endif
      return;
    }

  /* set the static fileptr in save.c to the highscore file pointer */
  set_fileptr(highscore_fp);

  i = 0;
  curpos = ftell (highscore_fp);
  rd_highscore(&old_entry);
  while (!feof(highscore_fp))
    {
      if (new_entry.points >= old_entry.points)
	break;
      /* under unix and VMS, only allow one sex/race/class combo per person,
	 on single user system, allow any number of entries, but try to
	 prevent multiple entries per character by checking for case when
	 birthdate/sex/race/class are the same, and died_from of scorefile
	 entry is "(saved)" */
      else if (((new_entry.uid != 0 && new_entry.uid == old_entry.uid)
		|| (new_entry.uid == 0 &&!strcmp(old_entry.died_from,"(saved)")
		    && new_entry.birth_date == old_entry.birth_date))
	       && new_entry.sex == old_entry.sex
	       && new_entry.race == old_entry.race
	       && new_entry.class == old_entry.class)
	{
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC) || defined(APOLLO)
	  (void) fclose (highscore_fp);
#endif
	  return;
	}
      else if (++i >= SCOREFILE_SIZE)
	{
	  /* only allow one thousand scores in the score file */
#if defined(MSDOS) || defined(VMS) || defined(AMIGA) || defined(MAC)
	  (void) fclose (highscore_fp);
#endif
	  return;
	}
      curpos = ftell (highscore_fp);
      rd_highscore(&old_entry);
    }

  if (feof(highscore_fp))
    {
      /* write out new_entry at end of file */
#ifndef BSD4_3
      (void) fseek (highscore_fp, curpos, L_SET);
#else
      (void) fseek (highscore_fp, (off_t)curpos, L_SET);
#endif
      wr_highscore(&new_entry);
    }
  else
    {
      entry = new_entry;
      while (!feof(highscore_fp))
	{
#ifndef BSD4_3
#if defined(ATARIST_TC) || defined(__TURBOC__)
	  /* No fseek with negative offset allowed.  */
	  (void) fseek(highscore_fp, (long)ftell(highscore_fp) -
		       sizeof(high_scores) - sizeof (char), L_SET);
#else
	  (void) fseek(highscore_fp,
		       -(long)sizeof(high_scores)-(long)sizeof(char),
		       L_INCR);
#endif
#else
	  (void) fseek(highscore_fp,
		       -(off_t)sizeof(high_scores)-(off_t)sizeof(char),
		       L_INCR);
#endif
	  wr_highscore(&entry);
	  /* under unix and VMS, only allow one sex/race/class combo per
	     person, on single user system, allow any number of entries, but
	     try to prevent multiple entries per character by checking for
	     case when birthdate/sex/race/class are the same, and died_from of
	     scorefile entry is "(saved)" */
	  if (((new_entry.uid != 0 && new_entry.uid == old_entry.uid)
		|| (new_entry.uid == 0 &&!strcmp(old_entry.died_from,"(saved)")
		    && new_entry.birth_date == old_entry.birth_date))
	      && new_entry.sex == old_entry.sex
	      && new_entry.race == old_entry.race
	      && new_entry.class == old_entry.class)
	    break;
	  entry = old_entry;
	  /* must fseek() before can change read/write mode */
#ifndef BSD4_3
#ifdef ATARIST_TC
	  /* No fseek relative to current position allowed.  */
	  (void) fseek(highscore_fp, (long)ftell(highscore_fp), L_SET);
#else
	  (void) fseek(highscore_fp, (long)0, L_INCR);
#endif
#else
	  (void) fseek(highscore_fp, (off_t)0, L_INCR);
#endif
	  curpos = ftell (highscore_fp);
	  rd_highscore(&old_entry);
	}
      if (feof(highscore_fp))
	{
#ifndef BSD4_3
	  (void) fseek (highscore_fp, curpos, L_SET);
#else
	  (void) fseek (highscore_fp, (off_t)curpos, L_SET);
#endif
	  wr_highscore(&entry);
	}
    }

#if !defined(VMS) && !defined(MSDOS) && !defined(AMIGA) && !defined(MAC) && !defined(APOLLO)
#ifdef ATARIST_TC
  /* Flock never called for Atari ST with TC.  */
#else
  (void) flock((int)fileno(highscore_fp), LOCK_UN);
#endif
#else
  (void) fclose (highscore_fp);
#endif
}


/* Change the player into a King!			-RAK-	 */
static void kingly()
{
  register struct misc *p_ptr;
  register char *p;

  /* Change the character attributes.		 */
  dun_level = 0;
  (void) strcpy(died_from, "Ripe Old Age");
  p_ptr = &py.misc;
  (void) restore_level ();
  p_ptr->lev += MAX_PLAYER_LEVEL;
  p_ptr->au += 250000L;
  p_ptr->max_exp += 5000000L;
  p_ptr->exp = p_ptr->max_exp;

  /* Let the player know that he did good.	 */
  clear_screen();
  put_buffer("#", 1, 34);
  put_buffer("#####", 2, 32);
  put_buffer("#", 3, 34);
  put_buffer(",,,  $$$  ,,,", 4, 28);
  put_buffer(",,=$   \"$$$$$\"   $=,,", 5, 24);
  put_buffer(",$$        $$$        $$,", 6, 22);
  put_buffer("*>         <*>         <*", 7, 22);
  put_buffer("$$         $$$         $$", 8, 22);
  put_buffer("\"$$        $$$        $$\"", 9, 22);
  put_buffer("\"$$       $$$       $$\"", 10, 23);
  p = "*#########*#########*";
  put_buffer(p, 11, 24);
  put_buffer(p, 12, 24);
  put_buffer("Veni, Vidi, Vici!", 15, 26);
  put_buffer("I came, I saw, I conquered!", 16, 21);
  if (p_ptr->male)
    put_buffer("All Hail the Mighty King!", 17, 22);
  else
    put_buffer("All Hail the Mighty Queen!", 17, 22);
  flush();
  pause_line(23);
}


/* Handles the gravestone end top-twenty routines	-RAK-	 */
void exit_game ()
{
#ifdef MAC
  /* Prevent strange things from happening */
  enablefilemenu(FALSE);
#endif

  /* What happens upon dying.				-RAK-	 */
  msg_print(CNIL);
  flush ();  /* flush all input */
  nosignals ();	 /* Can't interrupt or suspend. */
  /* If the game has been saved, then save sets turn back to -1, which
     inhibits the printing of the tomb.	 */
  if (turn >= 0)
    {
      if (total_winner)
	kingly();
      print_tomb();
    }
  if (character_generated && !character_saved)
#ifdef MAC
    (void) save_char (TRUE);		/* Save the memory at least. */
#else
    (void) save_char ();		/* Save the memory at least. */
#endif
  /* add score to scorefile if applicable */
  if (character_generated)
    {
      /* Clear character_saved, strange thing to do, but it prevents inkey()
	 from recursively calling exit_game() when there has been an eof
	 on stdin detected.  */
      character_saved = FALSE;
      highscores();
      display_scores (TRUE);
    }
  erase_line (23, 0);
  restore_term ();
#ifdef MAC
  /* Undo what has been done */
  enablefilemenu(TRUE);
  /* Long jump back into the Mac wrapper, in lieu of exit () */
  goback();
#else
  exit (0);
#endif
}
