/* NetHack 3.5	end.c	$NHDT-Date$  $NHDT-Branch$:$NHDT-Revision$ */
/* NetHack 3.5	end.c	$Date: 2012/04/09 02:56:30 $  $Revision: 1.79 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed.  See license for details. */

#define NEED_VARARGS	/* comment line for pre-compiled headers */

#include "hack.h"
#include "lev.h"
#ifndef NO_SIGNAL
#include <signal.h>
#endif
#include <ctype.h>
#include <limits.h>
#include "dlb.h"

/* add b to long a, convert wraparound to max value */
#define nowrap_add(a, b) (a = ((a + b) < 0 ? LONG_MAX : (a + b)))

	/* these probably ought to be generated by makedefs, like LAST_GEM */
#define FIRST_GEM    DILITHIUM_CRYSTAL
#define FIRST_AMULET AMULET_OF_ESP
#define LAST_AMULET  AMULET_OF_YENDOR
 
struct valuable_data { long count; int typ; };

static struct valuable_data
	gems[LAST_GEM+1 - FIRST_GEM + 1], /* 1 extra for glass */
	amulets[LAST_AMULET+1 - FIRST_AMULET];

static struct val_list { struct valuable_data *list; int size; } valuables[] = {
	{ gems,    sizeof gems / sizeof *gems },
	{ amulets, sizeof amulets / sizeof *amulets },
	{ 0, 0 }
};

#ifndef NO_SIGNAL
STATIC_PTR void FDECL(done_intr, (int));
# if defined(UNIX) || defined(VMS) || defined (__EMX__)
static void FDECL(done_hangup, (int));
# endif
#endif
STATIC_DCL void FDECL(disclose,(int,BOOLEAN_P));
STATIC_DCL void FDECL(get_valuables, (struct obj *));
STATIC_DCL void FDECL(sort_valuables, (struct valuable_data *,int));
STATIC_DCL void FDECL(artifact_score, (struct obj *,BOOLEAN_P,winid));
STATIC_DCL boolean FDECL(odds_and_ends, (struct obj *,int));
STATIC_DCL void FDECL(savelife, (int));
STATIC_DCL void FDECL(list_vanquished, (CHAR_P,BOOLEAN_P));
STATIC_DCL void FDECL(list_genocided, (CHAR_P,BOOLEAN_P));
STATIC_DCL boolean FDECL(should_query_disclose_option, (int,char *));

#if defined(__BEOS__) || defined(MICRO) || defined(WIN32) || defined(OS2)
extern void FDECL(nethack_exit,(int));
#else
# define nethack_exit exit
#endif

#define done_stopprint program_state.stopprint

#ifndef PANICTRACE
# define NH_abort NH_abort_
#endif

#ifdef AMIGA
# define NH_abort_()	Abort(0)
#else
# ifdef SYSV
#  define NH_abort_()	(void) abort()
# else
#  ifdef WIN32
#   define NH_abort_()	win32_abort()
#  else
#   define NH_abort_()	abort()
#  endif
# endif  /* !SYSV */
#endif   /* !AMIGA */

#ifdef PANICTRACE
# include <errno.h>
# ifdef PANICTRACE_LIBC
#  include <execinfo.h>
# endif

/* What do we try and in what order?  Tradeoffs:
 * libc: +no external programs required
 *        -requires newish libc/glibc
 *        -requires -rdynamic
 * gdb:   +gives more detailed information
 *        +works on more OS versions
 *        -requires -g, which may preclude -O on some compilers
 */
# ifdef SYSCF
#  define SYSOPT_PANICTRACE_GDB sysopt.panictrace_gdb
#  ifdef PANICTRACE_LIBC
#   define SYSOPT_PANICTRACE_LIBC sysopt.panictrace_libc
#  else
#   define SYSOPT_PANICTRACE_LIBC 0
#  endif
# else
#  define SYSOPT_PANICTRACE_GDB (nh_getenv("NETHACK_USE_GDB")==0?0:2)
#  ifdef PANICTRACE_LIBC
#   define SYSOPT_PANICTRACE_LIBC 1
#  else
#   define SYSOPT_PANICTRACE_LIBC 0
#  endif
# endif

static void NDECL(NH_abort);
# ifndef NO_SIGNAL
static void FDECL(panictrace_handler, (int));
# endif
static boolean NDECL(NH_panictrace_libc);
static boolean NDECL(NH_panictrace_gdb);

# ifndef NO_SIGNAL
/*ARGSUSED*/
void
panictrace_handler(sig_unused)   /* called as signal() handler, so sent at least one arg */
int sig_unused UNUSED;
{
#  define SIG_MSG "\nSignal received.\n"
	(void) write(2, SIG_MSG, sizeof(SIG_MSG)-1);
	NH_abort();
}

void
panictrace_setsignals(set)
boolean set;
{
#  define SETSIGNAL(sig) (void) signal(sig, set?(SIG_RET_TYPE)panictrace_handler:SIG_DFL);
#  ifdef SIGILL
	SETSIGNAL(SIGILL);
#  endif
#  ifdef SIGTRAP
	SETSIGNAL(SIGTRAP);
#  endif
#  ifdef SIGIOT
	SETSIGNAL(SIGIOT);
#  endif
#  ifdef SIGBUS
	SETSIGNAL(SIGBUS);
#  endif
#  ifdef SIGFPE
	SETSIGNAL(SIGFPE);
#  endif
#  ifdef SIGSEGV
	SETSIGNAL(SIGSEGV);
#  endif
#  ifdef SIGSTKFLT
	SETSIGNAL(SIGSTKFLT);
#  endif
#  ifdef SIGSYS
	SETSIGNAL(SIGSYS);
#  endif
#  ifdef SIGEMT
	SETSIGNAL(SIGEMT);
#  endif
#  undef SETSIGNAL
}
# endif	/* NO_SIGNAL */

static void
NH_abort()
{
	int gdb_prio = SYSOPT_PANICTRACE_GDB;
	int libc_prio = SYSOPT_PANICTRACE_LIBC;
	static boolean aborting = FALSE;

	if(aborting) return;
	aborting = TRUE;

# ifndef VMS
	if(gdb_prio == libc_prio && gdb_prio > 0) gdb_prio++;

	if(gdb_prio > libc_prio){
		NH_panictrace_gdb() || (libc_prio && NH_panictrace_libc());
	} else {
		NH_panictrace_libc() || (gdb_prio && NH_panictrace_gdb());
	}

# else /* VMS */
	/* overload otherwise unused priority for debug mode: 1 = show
	   traceback and exit; 2 = show traceback and stay in debugger */
     /* if (wizard && gdb_prio == 1) gdb_prio = 2; */
	vms_traceback(gdb_prio);
	(void)libc_prio;	/* half-hearted attempt at lint suppression */

# endif /* ?VMS */

# ifndef NO_SIGNAL
	panictrace_setsignals(FALSE);
# endif
	NH_abort_();
}

static boolean
NH_panictrace_libc()
{
# ifdef PANICTRACE_LIBC
	void *bt[20];
	size_t count, x;
	char **info;

	raw_print("Generating more information you may report:\n");
	count = backtrace(bt, SIZE(bt));
	info = backtrace_symbols(bt, count);
	for (x = 0; x < count; x++) {
	    raw_printf("[%lu] %s", (unsigned long)x, info[x]);
	}
	/* free(info);	Don't risk it. */
	return TRUE;
# else
	return FALSE;
# endif  /* !PANICTRACE_LIBC */
}

/*
 *   fooPATH  file system path for foo
 *   fooVAR   (possibly const) variable containing fooPATH
 */
# ifdef PANICTRACE_GDB
#  ifdef SYSCF
#   define GDBVAR sysopt.gdbpath
#   define GREPVAR sysopt.greppath
#  else /* SYSCF */
#   define GDBVAR GDBPATH
#   define GREPVAR GREPPATH
#  endif /* SYSCF */
# endif /* PANICTRACE_GDB */

static boolean
NH_panictrace_gdb()
{
# ifdef PANICTRACE_GDB
	/* A (more) generic method to get a stack trace - invoke
	 * gdb on ourself. */
	char *gdbpath = GDBVAR;
	char *greppath = GREPVAR;
	char buf[BUFSZ];
	FILE *gdb;

	if(gdbpath == NULL || gdbpath[0] == 0) return FALSE;
	if(greppath == NULL || greppath[0] == 0) return FALSE;

	sprintf(buf, "%s -n -q %s %d 2>&1 | %s '^#'",
			gdbpath, ARGV0, getpid(), greppath);
	gdb = popen(buf, "w");
	if(gdb){
		raw_print("Generating more information you may report:\n");
		fprintf(gdb, "bt\nquit\ny");
		fflush(gdb);
		sleep(4);	/* ugly */
		pclose(gdb);
		return TRUE;
	} else {
		return FALSE;
	}
# else
	return FALSE;
# endif  /* !PANICTRACE_GDB */
}
#endif  /* PANICTRACE */

/*
 * The order of these needs to match the macros in hack.h.
 */
static NEARDATA const char *deaths[] = {		/* the array of death */
	"died", "choked", "poisoned", "starvation", "drowning",
	"burning", "dissolving under the heat and pressure",
	"crushed", "turned to stone", "turned into slime",
	"genocided", "panic", "trickery",
	"quit", "escaped", "ascended"
};

static NEARDATA const char *ends[] = {		/* "when you..." */
	"died", "choked", "were poisoned", "starved", "drowned",
	"burned", "dissolved in the lava",
	"were crushed", "turned to stone", "turned into slime",
	"were genocided", "panicked", "were tricked",
	"quit", "escaped", "ascended"
};

static boolean Schroedingers_cat = FALSE;

/*ARGSUSED*/
void
done1(sig_unused)   /* called as signal() handler, so sent at least one arg */
int sig_unused UNUSED;
{
#ifndef NO_SIGNAL
	(void) signal(SIGINT,SIG_IGN);
#endif
	if(flags.ignintr) {
#ifndef NO_SIGNAL
		(void) signal(SIGINT, (SIG_RET_TYPE) done1);
#endif
		clear_nhwindow(WIN_MESSAGE);
		curs_on_u();
		wait_synch();
		if(multi > 0) nomul(0);
	} else {
		(void)done2();
	}
}


/* "#quit" command or keyboard interrupt */
int
done2()
{
	if (!paranoid_query(ParanoidQuit, "Really quit?")) {
#ifndef NO_SIGNAL
		(void) signal(SIGINT, (SIG_RET_TYPE) done1);
#endif
		clear_nhwindow(WIN_MESSAGE);
		curs_on_u();
		wait_synch();
		if(multi > 0) nomul(0);
		if(multi == 0) {
		    u.uinvulnerable = FALSE;	/* avoid ctrl-C bug -dlc */
		    u.usleep = 0;
		}
		return 0;
	}
#if (defined(UNIX) || defined(VMS) || defined(LATTICE))
	if(wizard) {
	    int c;
# ifdef VMS
	    extern int debuggable;	/* sys/vms/vmsmisc.c, vmsunix.c */

	    c = !debuggable ? 'n' : ynq("Enter debugger?");
# else
#  ifdef LATTICE
	    c = ynq("Create SnapShot?");
#  else
	    c = ynq("Dump core?");
#  endif
# endif
	    if (c == 'y') {
# ifndef NO_SIGNAL
		(void) signal(SIGINT, (SIG_RET_TYPE) done1);
# endif
		exit_nhwindows((char *)0);
		NH_abort();
	    } else if (c == 'q') done_stopprint++;
	}
#endif
#ifndef LINT
	done(QUIT);
#endif
	return 0;
}

#ifndef NO_SIGNAL
/*ARGSUSED*/
STATIC_PTR void
done_intr(sig_unused) /* called as signal() handler, so sent at least one arg */
int sig_unused UNUSED;
{
	done_stopprint++;
	(void) signal(SIGINT, SIG_IGN);
# if defined(UNIX) || defined(VMS)
	(void) signal(SIGQUIT, SIG_IGN);
# endif
	return;
}

# if defined(UNIX) || defined(VMS) || defined(__EMX__)
static void
done_hangup(sig)	/* signal() handler */
int sig;
{
	program_state.done_hup++;
	sethanguphandler((void FDECL((*),(int)))SIG_IGN);
	done_intr(sig);
	return;
}
# endif
#endif /* NO_SIGNAL */

void
done_in_by(mtmp, how)
struct monst *mtmp;
int how;
{
	char buf[BUFSZ];
	struct permonst *mptr = mtmp->data,
		*champtr = (mtmp->cham >= LOW_PM) ? &mons[mtmp->cham] : mptr;
	boolean distorted = (boolean)(Hallucination && canspotmon(mtmp)),
		mimicker = (mtmp->m_ap_type == M_AP_MONSTER),
		imitator = (mptr != champtr || mimicker);

	You((how == STONING) ? "turn to stone..." : "die...");
	mark_synch();	/* flush buffered screen output */
	buf[0] = '\0';
	killer.format = KILLED_BY_AN;
	/* "killed by the high priest of Crom" is okay,
	   "killed by the high priest" alone isn't */
	if ((mptr->geno & G_UNIQ) != 0 &&
		!(imitator && !mimicker) &&
		!(mptr == &mons[PM_HIGH_PRIEST] && !mtmp->ispriest)) {
	    if (!type_is_pname(mptr))
		Strcat(buf, "the ");
	    killer.format = KILLED_BY;
	}
	/* _the_ <invisible> <distorted> ghost of Dudley */
	if (mptr == &mons[PM_GHOST] && has_mname(mtmp)) {
		Strcat(buf, "the ");
		killer.format = KILLED_BY;
	}
	if (mtmp->minvis)
		Strcat(buf, "invisible ");
	if (distorted)
		Strcat(buf, "hallucinogen-distorted ");

	if (imitator) {
		char shape[BUFSZ];
		const char *realnm = champtr->mname, *fakenm = mptr->mname;
		boolean alt = is_vampshifter(mtmp);

		if (mimicker) {
		    /* realnm is already correct because champtr==mptr;
		       set up fake mptr for type_is_pname/the_unique_pm */
		    mptr = &mons[mtmp->mappearance];
		    fakenm = mptr->mname;
		} else if (alt && strstri(realnm, "vampire") &&
			!strcmp(fakenm, "vampire bat")) {
		    /* special case: use "vampire in bat form" in preference
		       to redundant looking "vampire in vampire bat form" */
		    fakenm = "bat";
		}
		/* for the alternate format, always suppress any article;
		   pname and the_unique should also have s_suffix() applied,
		   but vampires don't take on any shapes which warrant that */
		if (alt || type_is_pname(mptr)) /* no article */
		    Strcpy(shape, fakenm);
		else if (the_unique_pm(mptr)) /* "the"; don't use the() here */
		    Sprintf(shape, "the %s", fakenm);
		else /* "a"/"an" */
		    Strcpy(shape, an(fakenm));
		/* omit "called" to avoid excessive verbosity */
		Sprintf(eos(buf),
			alt ? "%s in %s form" :
			  mimicker ? "%s disguised as %s" : "%s imitating %s",
			realnm, shape);
		mptr = mtmp->data;	/* reset for mimicker case */
	} else if (mptr == &mons[PM_GHOST]) {
		Strcat(buf, "ghost");
		if (has_mname(mtmp)) Sprintf(eos(buf), " of %s", MNAME(mtmp));
	} else if(mtmp->isshk) {
		const char *shknm = shkname(mtmp),
			   *honorific = shkname_is_pname(mtmp) ? "" :
					    mtmp->female ? "Ms. " : "Mr. ";

		Sprintf(eos(buf), "%s%s, the shopkeeper", honorific, shknm);
		killer.format = KILLED_BY;
	} else if (mtmp->ispriest || mtmp->isminion) {
		/* m_monnam() suppresses "the" prefix plus "invisible", and
		   it overrides the effect of Hallucination on priestname() */
		Strcat(buf, m_monnam(mtmp));
	} else {
		Strcat(buf, mptr->mname);
		if (has_mname(mtmp))
		    Sprintf(eos(buf), " called %s", MNAME(mtmp));
	}

	if (multi) Strcat(buf, ", while helpless");
	Strcpy(killer.name, buf);
	if (mptr->mlet == S_WRAITH)
		u.ugrave_arise = PM_WRAITH;
	else if (mptr->mlet == S_MUMMY && urace.mummynum != NON_PM)
		u.ugrave_arise = urace.mummynum;
	else if (mptr->mlet == S_VAMPIRE && Race_if(PM_HUMAN))
		u.ugrave_arise = PM_VAMPIRE;
	else if (mptr == &mons[PM_GHOUL])
		u.ugrave_arise = PM_GHOUL;
	/* this could happen if a high-end vampire kills the hero
	   when ordinary vampires are genocided; ditto for wraiths */
	if (u.ugrave_arise >= LOW_PM &&
				(mvitals[u.ugrave_arise].mvflags & G_GENOD))
		u.ugrave_arise = NON_PM;

	done(how);
	return;
}

#if defined(WIN32) && !defined(SYSCF)
# define NOTIFY_NETHACK_BUGS
#endif

/*VARARGS1*/
void
panic VA_DECL(const char *, str)
	VA_START(str);
	VA_INIT(str, char *);

	if (program_state.panicking++)
	    NH_abort();	/* avoid loops - this should never happen*/

	if (iflags.window_inited) {
	    raw_print("\r\nOops...");
	    wait_synch();	/* make sure all pending output gets flushed */
	    exit_nhwindows((char *)0);
	    iflags.window_inited = 0; /* they're gone; force raw_print()ing */
	}

	raw_print(program_state.gameover ?
		  "Postgame wrapup disrupted." :
		  !program_state.something_worth_saving ?
		  "Program initialization has failed." :
		  "Suddenly, the dungeon collapses.");
#ifndef MICRO
# if defined(NOTIFY_NETHACK_BUGS)
	if (!wizard)
	    raw_printf("Report the following error to \"%s\" or at \"%s\".",
			DEVTEAM_EMAIL, DEVTEAM_URL);
	else if (program_state.something_worth_saving)
	    raw_print("\nError save file being written.\n");
# else
	if (!wizard){
	    if(sysopt.support)
		raw_printf("To report this error, %s%s.", sysopt.support,
			!program_state.something_worth_saving ? "" :
			" and it may be possible to rebuild.");
	    else if(sysopt.wizards){
		char *tmp = build_english_list(sysopt.wizards);
		raw_printf("To report this error, contact %s%s", tmp,
			!program_state.something_worth_saving ? "" :
			" and it may be possible to rebuild.");
		free(tmp);
	    } else
		raw_printf("Report error to \"%s\"%s.",
			WIZARD_NAME,
			!program_state.something_worth_saving ? "" :
			" and it may be possible to rebuild.");
	}
# endif
	/* XXX can we move this above the prints?  Then we'd be able to
	 * suppress "it may be possible to rebuild" based on dosave0()
	 * or say it's NOT possible to rebuild. */
	if (program_state.something_worth_saving) {
	    set_error_savefile();
	    if(dosave0()){
		    /* os/win port specific recover instructions */
		if(sysopt.recover) raw_printf("%s", sysopt.recover);
	    }
	}
#endif
	{
	    char buf[BUFSZ];
	    Vsprintf(buf,str,VA_ARGS);
	    raw_print(buf);
	    paniclog("panic", buf);
	}
#ifdef WIN32
	interject(INTERJECT_PANIC);
#endif
#if defined(UNIX) || defined(VMS) || defined(LATTICE) || defined(WIN32)
	if (wizard)
	    NH_abort();	/* generate core dump */
#endif
	VA_END();
	done(PANICKED);
}

STATIC_OVL boolean
should_query_disclose_option(category, defquery)
int category;
char *defquery;
{
    int idx;
    char *dop = index(disclosure_options, category);

    if (dop && defquery) {
	idx = (int)(dop - disclosure_options);
	if (idx < 0 || idx > (NUM_DISCLOSURE_OPTIONS - 1)) {
	    impossible(
		   "should_query_disclose_option: bad disclosure index %d %c",
		       idx, category);
	    *defquery = DISCLOSE_PROMPT_DEFAULT_YES;
	    return TRUE;
	}
	if (flags.end_disclose[idx] == DISCLOSE_YES_WITHOUT_PROMPT) {
	    *defquery = 'y';
	    return FALSE;
	} else if (flags.end_disclose[idx] == DISCLOSE_NO_WITHOUT_PROMPT) {
	    *defquery = 'n';
	    return FALSE;
	} else if (flags.end_disclose[idx] == DISCLOSE_PROMPT_DEFAULT_YES) {
	    *defquery = 'y';
	    return TRUE;
	} else {
	    *defquery = 'n';
	    return TRUE;
	}
    }
    if (defquery)
	impossible("should_query_disclose_option: bad category %c", category);
    else
	impossible("should_query_disclose_option: null defquery");
    return TRUE;
}

STATIC_OVL void
disclose(how,taken)
int how;
boolean taken;
{
	char	c = 0, defquery;
	char	qbuf[QBUFSZ];
	boolean ask;

	if (invent && !done_stopprint) {
	    if (taken)
		Sprintf(qbuf, "Do you want to see what you had when you %s?",
			(how == QUIT) ? "quit" : "died");
	    else
		Strcpy(qbuf, "Do you want your possessions identified?");

	    ask = should_query_disclose_option('i', &defquery);
	    c = ask ? yn_function(qbuf, ynqchars, defquery) : defquery;
	    if (c == 'y') {
		struct obj *obj;
		
		for (obj = invent; obj; obj = obj->nobj) {
		    makeknown(obj->otyp);
		    obj->known = obj->bknown = obj->dknown = obj->rknown = 1;
		    if (Is_container(obj) || obj->otyp == STATUE)
			obj->cknown = obj->lknown = 1;
		}
		(void) display_inventory((char *)0, TRUE);
		container_contents(invent, TRUE, TRUE, FALSE);
	    }
	    if (c == 'q')  done_stopprint++;
	}

	if (!done_stopprint) {
	    ask = should_query_disclose_option('a', &defquery);
	    c = ask ? yn_function("Do you want to see your attributes?",
				  ynqchars, defquery) : defquery;
	    if (c == 'y')
		enlightenment((BASICENLIGHTENMENT|MAGICENLIGHTENMENT),
		    (how >= PANICKED) ? ENL_GAMEOVERALIVE : ENL_GAMEOVERDEAD);
	    if (c == 'q') done_stopprint++;
	}

	if (!done_stopprint) {
	    ask = should_query_disclose_option('v', &defquery);
	    list_vanquished(defquery, ask);
	}

	if (!done_stopprint) {
	    ask = should_query_disclose_option('g', &defquery);
	    list_genocided(defquery, ask);
	}

	if (!done_stopprint) {
	    ask = should_query_disclose_option('c', &defquery);
	    c = ask ? yn_function("Do you want to see your conduct?",
				  ynqchars, defquery) : defquery;
	    if (c == 'y')
		show_conduct((how >= PANICKED) ? 1 : 2);
	    if (c == 'q') done_stopprint++;
	}

	if (!done_stopprint) {
	    ask = should_query_disclose_option('o', &defquery);
	    c = ask ? yn_function("Do you want to see the dungeon overview?",
				  ynqchars, defquery) : defquery;
	    if (c == 'y')
		show_overview((how >= PANICKED) ? 1 : 2, how);
	    if (c == 'q') done_stopprint++;
	}
}

/* try to get the player back in a viable state after being killed */
STATIC_OVL void
savelife(how)
int how;
{
	int uhpmin = max(2 * u.ulevel, 10);

	if (u.uhpmax < uhpmin) u.uhpmax = uhpmin;
	u.uhp = u.uhpmax;
	u.uswldtim = 0;
	if (u.uhunger < 500) {
	    u.uhunger = 500;
	    newuhs(FALSE);
	}
	/* cure impending doom of sickness hero won't have time to fix */
	if ((Sick & TIMEOUT) == 1L) {
	    u.usick_type = 0;
	    set_itimeout(&Sick, 0L);
	}
	if (how == CHOKING) init_uhunger();
	nomovemsg = "You survived that attempt on your life.";
	context.move = 0;
	if(multi > 0) multi = 0; else multi = -1;
	if(u.utrap && u.utraptype == TT_LAVA) u.utrap = 0;
	context.botl = 1;
	u.ugrave_arise = NON_PM;
	HUnchanging = 0L;
	curs_on_u();
}

/*
 * Get valuables from the given list.  Revised code: the list always remains
 * intact.
 */
STATIC_OVL void
get_valuables(list)
struct obj *list;	/* inventory or container contents */
{
    register struct obj *obj;
    register int i;

    /* find amulets and gems, ignoring all artifacts */
    for (obj = list; obj; obj = obj->nobj)
	if (Has_contents(obj)) {
	    get_valuables(obj->cobj);
	} else if (obj->oartifact) {
	    continue;
	} else if (obj->oclass == AMULET_CLASS) {
	    i = obj->otyp - FIRST_AMULET;
	    if (!amulets[i].count) {
		amulets[i].count = obj->quan;
		amulets[i].typ = obj->otyp;
	    } else amulets[i].count += obj->quan; /* always adds one */
	} else if (obj->oclass == GEM_CLASS && obj->otyp < LUCKSTONE) {
	    i = min(obj->otyp, LAST_GEM + 1) - FIRST_GEM;
	    if (!gems[i].count) {
		gems[i].count = obj->quan;
		gems[i].typ = obj->otyp;
	    } else gems[i].count += obj->quan;
	}
    return;
}

/*
 *  Sort collected valuables, most frequent to least.  We could just
 *  as easily use qsort, but we don't care about efficiency here.
 */
STATIC_OVL void
sort_valuables(list, size)
struct valuable_data list[];
int size;		/* max value is less than 20 */
{
    register int i, j;
    struct valuable_data ltmp;

    /* move greater quantities to the front of the list */
    for (i = 1; i < size; i++) {
	if (list[i].count == 0) continue;	/* empty slot */
	ltmp = list[i]; /* structure copy */
	for (j = i; j > 0; --j)
	    if (list[j-1].count >= ltmp.count) break;
	    else {
		list[j] = list[j-1];
	    }
	list[j] = ltmp;
    }
    return;
}

#define CAT_CHECK 2

STATIC_OVL boolean
odds_and_ends(list, what)
struct obj *list;
int what;
{
    struct obj *otmp;
    for (otmp = list; otmp; otmp = otmp->nobj) {
	switch(what) {
		case CAT_CHECK:			 /* Schroedinger's Cat */
			/* Ascending is deterministic */
			if (SchroedingersBox(otmp))
				return rn2(2);
			break;
	}
	if (Has_contents(otmp))
	    return odds_and_ends(otmp->cobj, what);
    }
    return FALSE;
}

/* called twice; first to calculate total, then to list relevant items */
STATIC_OVL void
artifact_score(list, counting, endwin)
struct obj *list;
boolean counting;	/* true => add up points; false => display them */
winid endwin;
{
    char pbuf[BUFSZ];
    struct obj *otmp;
    long value, points;
    short dummy;	/* object type returned by artifact_name() */

    for (otmp = list; otmp; otmp = otmp->nobj) {
	if (otmp->oartifact ||
			otmp->otyp == BELL_OF_OPENING ||
			otmp->otyp == SPE_BOOK_OF_THE_DEAD ||
			otmp->otyp == CANDELABRUM_OF_INVOCATION) {
	    value = arti_cost(otmp);	/* zorkmid value */
	    points = value * 5 / 2;	/* score value */
	    if (counting) {
		nowrap_add(u.urexp, points);
	    } else {
		makeknown(otmp->otyp);
		otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1;
		/* assumes artifacts don't have quan > 1 */
		Sprintf(pbuf, "%s%s (worth %ld %s and %ld points)",
			the_unique_obj(otmp) ? "The " : "",
			otmp->oartifact ? artifact_name(xname(otmp), &dummy) :
				OBJ_NAME(objects[otmp->otyp]),
			value, currency(value), points);
		putstr(endwin, 0, pbuf);
	    }
	}
	if (Has_contents(otmp))
	    artifact_score(otmp->cobj, counting, endwin);
    }
}

/* Be careful not to call panic from here! */
void
done(how)
int how;
{
	boolean taken;
	char pbuf[BUFSZ];
	winid endwin = WIN_ERR;
	boolean bones_ok, have_windows = iflags.window_inited;
	struct obj *corpse = (struct obj *)0;
	time_t endtime;
	long umoney;
	long tmp;

	if (how == TRICKED) {
	    if (killer.name[0]) {
		paniclog("trickery", killer.name);
		killer.name[0] = 0;
	    }
	    if (wizard) {
		You("are a very tricky wizard, it seems.");
		return;
	    }
	}

	/* pbuf: holds Sprintf'd output for raw_print and putstr
	 */
	if (how == ASCENDED || (!killer.name[0] && how == GENOCIDED))
		killer.format = NO_KILLER_PREFIX;
	/* Avoid killed by "a" burning or "a" starvation */
	if (!killer.name[0] && (how == STARVING || how == BURNING))
		killer.format = KILLED_BY;
	if (!killer.name[0] || how >= PANICKED)
	    Strcpy(killer.name, deaths[how]);

	if (how < PANICKED) u.umortality++;
	if (Lifesaved && (how <= GENOCIDED)) {
		pline("But wait...");
		makeknown(AMULET_OF_LIFE_SAVING);
		Your("medallion %s!",
		      !Blind ? "begins to glow" : "feels warm");
		if (how == CHOKING) You("vomit ...");
		You_feel("much better!");
		pline_The("medallion crumbles to dust!");
		if (uamul) useup(uamul);

		(void) adjattrib(A_CON, -1, TRUE);
		savelife(how);
		if (how == GENOCIDED)
			pline("Unfortunately you are still genocided...");
		else {
			killer.name[0] = 0;
			killer.format = 0;
			return;
		}
	}
	if ((wizard || discover) && (how <= GENOCIDED)) {
		if (paranoid_query(ParanoidDie, "Die?")) goto die;
		pline("OK, so you don't %s.",
			(how == CHOKING) ? "choke" : "die");
		savelife(how);
		killer.name[0] = 0;
		killer.format = 0;
		return;
	}

    /*
     *	The game is now over...
     */

die:
	program_state.gameover = 1;
	/* in case of a subsequent panic(), there's no point trying to save */
	program_state.something_worth_saving = 0;
	/* render vision subsystem inoperative */
	iflags.vision_inited = 0;
	/* might have been killed while using a disposable item, so make sure
	   it's gone prior to inventory disclosure and creation of bones data */
	inven_inuse(TRUE);
	/* maybe not on object lists; if an active light source, would cause
	   big trouble (`obj_is_local' panic) for savebones() -> savelev() */
	if (thrownobj && thrownobj->where == OBJ_FREE) dealloc_obj(thrownobj);
	if (kickedobj && kickedobj->where == OBJ_FREE) dealloc_obj(kickedobj);

	/* remember time of death here instead of having bones, rip, and
	   topten figure it out separately and possibly getting different
           time or even day if player is slow responding to --More-- */
	endtime = getnow();

	/* Sometimes you die on the first move.  Life's not fair.
	 * On those rare occasions you get hosed immediately, go out
	 * smiling... :-)  -3.
	 */
	if (moves <= 1 && how < PANICKED)	/* You die... --More-- */
	    pline("Do not pass go.  Do not collect 200 %s.", currency(200L));

	if (have_windows) wait_synch();	/* flush screen output */
#ifndef NO_SIGNAL
	(void) signal(SIGINT, (SIG_RET_TYPE) done_intr);
# if defined(UNIX) || defined(VMS) || defined (__EMX__)
	(void) signal(SIGQUIT, (SIG_RET_TYPE) done_intr);
	sethanguphandler(done_hangup);
# endif
#endif /* NO_SIGNAL */

	bones_ok = (how < GENOCIDED) && can_make_bones();

	if (bones_ok && launch_in_progress()) force_launch_placement();

	/* maintain ugrave_arise even for !bones_ok */
	if (how == BURNING || how == DISSOLVED) /* corpse gets burnt up too */
	    u.ugrave_arise = (NON_PM - 2);	/* leave no corpse */
	else if (how == STONING)
	    u.ugrave_arise = (NON_PM - 1);	/* statue instead of corpse */
	else if (how == TURNED_SLIME)
	    u.ugrave_arise = PM_GREEN_SLIME;

	/* if pets will contribute to score, populate mydogs list now
	   (bones creation isn't a factor, but pline() messaging is) */
	if (how == ESCAPED || how == ASCENDED) keepdogs(TRUE);

	if (how == QUIT) {
	    killer.format = NO_KILLER_PREFIX;
	    if (u.uhp < 1) {
		how = DIED;
		u.umortality++;	/* skipped above when how==QUIT */
		Strcpy(killer.name, "quit while already on Charon's boat");
	    }
	}
	if (how == ESCAPED || how == PANICKED)
	    killer.format = NO_KILLER_PREFIX;

	if (how != PANICKED) {
	    /* these affect score and/or bones, but avoid them during panic */
	    taken = paybill((how == ESCAPED) ? -1 : (how != QUIT));
	    paygd();
	    clearpriests();
	} else
	    taken = FALSE;	/* lint; assert( !bones_ok ); */

	clearlocks();

	if (have_windows) display_nhwindow(WIN_MESSAGE, FALSE);

	if (strcmp(flags.end_disclose, "none") && how != PANICKED)
	    disclose(how, taken);

	/* finish_paybill should be called after disclosure but before bones */
	if (bones_ok && taken) finish_paybill();

	/* grave creation should be after disclosure so it doesn't have
	   this grave in the current level's features for #overview */
	if (bones_ok && u.ugrave_arise == NON_PM &&
		!(mvitals[u.umonnum].mvflags & G_NOCORPSE)) {
	    int mnum = u.umonnum;

	    if (!Upolyd) {
		/* Base corpse on race when not poly'd since original
		 * u.umonnum is based on role, and all role monsters
		 * are human.
		 */
		mnum = (flags.female && urace.femalenum != NON_PM) ?
				urace.femalenum : urace.malenum;
	    }
	    corpse = mk_named_object(CORPSE, &mons[mnum],
				     u.ux, u.uy, plname);
	    Sprintf(pbuf, "%s, ", plname);
	    formatkiller(eos(pbuf), sizeof pbuf - strlen(pbuf), how);
	    make_grave(u.ux, u.uy, pbuf);
	}

	/* calculate score, before creating bones [container gold] */
	{
	    int deepest = deepest_lev_reached(FALSE);

	    umoney = money_cnt(invent);
	    tmp = u.umoney0;
	    umoney += hidden_gold();	/* accumulate gold from containers */
	    tmp = umoney - tmp;		/* net gain */

	    if (tmp < 0L)
		tmp = 0L;
	    if (how < PANICKED)
		tmp -= tmp / 10L;
	    tmp += 50L * (long)(deepest - 1);
	    if (deepest > 20)
		tmp += 1000L * (long)((deepest > 30) ? 10 : deepest - 20);
	    nowrap_add(u.urexp, tmp);

	    /* ascension gives a score bonus iff offering to original deity */
	    if (how == ASCENDED && u.ualign.type == u.ualignbase[A_ORIGINAL]) {
		/* retaining original alignment: score *= 2;
		   converting, then using helm-of-OA to switch back: *= 1.5 */
		tmp = (u.ualignbase[A_CURRENT] == u.ualignbase[A_ORIGINAL]) ?
			u.urexp : (u.urexp / 2L);
		nowrap_add(u.urexp, tmp);
	    }
	}

	if (u.ugrave_arise >= LOW_PM && u.ugrave_arise != PM_GREEN_SLIME) {
	    /* give this feedback even if bones aren't going to be created,
	       so that its presence or absence doesn't tip off the player to
	       new bones or their lack; it might be a lie if makemon fails */
	    Your("body rises from the dead as %s...",
		 an(mons[u.ugrave_arise].mname));
	    display_nhwindow(WIN_MESSAGE, FALSE);
	}

	if (bones_ok) {
	    if (!wizard || paranoid_query(ParanoidBones, "Save bones?"))
		savebones(how, endtime, corpse);
	    /* corpse may be invalid pointer now so
		ensure that it isn't used again */
	    corpse = (struct obj *)0;
	}

	/* update gold for the rip output, which can't use hidden_gold()
	   (containers will be gone by then if bones just got saved...) */
	done_money = umoney;

	/* clean up unneeded windows */
	if (have_windows) {
	    wait_synch();
	    display_nhwindow(WIN_MESSAGE, TRUE);
	    destroy_nhwindow(WIN_MAP);
#ifndef STATUS_VIA_WINDOWPORT
	    destroy_nhwindow(WIN_STATUS);
#endif
	    destroy_nhwindow(WIN_MESSAGE);
#ifdef STATUS_VIA_WINDOWPORT
	    WIN_MESSAGE = WIN_MAP = WIN_ERR;
#else
	    WIN_MESSAGE = WIN_STATUS = WIN_MAP = WIN_ERR;
#endif
	    if(!done_stopprint || flags.tombstone)
		endwin = create_nhwindow(NHW_TEXT);

	    if (how < GENOCIDED && flags.tombstone && endwin != WIN_ERR)
	      outrip(endwin, how, endtime);
	} else
	    done_stopprint = 1; /* just avoid any more output */

	if (u.uhave.amulet) Strcat(killer.name, " (with the Amulet)");
	else if (how == ESCAPED) {
	    if (Is_astralevel(&u.uz))	/* offered Amulet to wrong deity */
		Strcat(killer.name, " (in celestial disgrace)");
	    else if (carrying(FAKE_AMULET_OF_YENDOR))
		Strcat(killer.name, " (with a fake Amulet)");
		/* don't bother counting to see whether it should be plural */
	}

	if (!done_stopprint) {
	    Sprintf(pbuf, "%s %s the %s...", Goodbye(), plname,
		   how != ASCENDED ?
		      (const char *) ((flags.female && urole.name.f) ?
		         urole.name.f : urole.name.m) :
		      (const char *) (flags.female ? "Demigoddess" : "Demigod"));
	    putstr(endwin, 0, pbuf);
	    putstr(endwin, 0, "");
	}

	if (how == ESCAPED || how == ASCENDED) {
	    register struct monst *mtmp;
	    register struct obj *otmp;
	    register struct val_list *val;
	    register int i;

	    for (val = valuables; val->list; val++)
		for (i = 0; i < val->size; i++) {
		    val->list[i].count = 0L;
		}
	    get_valuables(invent);

	    /* add points for collected valuables */
	    for (val = valuables; val->list; val++)
		for (i = 0; i < val->size; i++)
		    if (val->list[i].count != 0L) {
			tmp = val->list[i].count
				  * (long)objects[val->list[i].typ].oc_cost;
			nowrap_add(u.urexp, tmp);
		    }

	    /* count the points for artifacts */
	    artifact_score(invent, TRUE, endwin);

	    viz_array[0][0] |= IN_SIGHT; /* need visibility for naming */
	    mtmp = mydogs;
	    if (!done_stopprint) Strcpy(pbuf, "You");
	    if (!Schroedingers_cat) /* check here in case disclosure was off */
		Schroedingers_cat = odds_and_ends(invent, CAT_CHECK);
	    if (Schroedingers_cat) {
		int mhp, m_lev = adj_lev(&mons[PM_HOUSECAT]);
		mhp = d(m_lev, 8);
		nowrap_add(u.urexp, mhp);
		if (!done_stopprint)
			Strcat(eos(pbuf), " and Schroedinger's cat");
	    }
	    if (mtmp) {
		while (mtmp) {
		    if (!done_stopprint)
			Sprintf(eos(pbuf), " and %s", mon_nam(mtmp));
		    if (mtmp->mtame)
			nowrap_add(u.urexp, mtmp->mhp);
		    mtmp = mtmp->nmon;
		}
		if (!done_stopprint) putstr(endwin, 0, pbuf);
		pbuf[0] = '\0';
	    } else {
		if (!done_stopprint) Strcat(pbuf, " ");
	    }
	    if (!done_stopprint) {
		Sprintf(eos(pbuf), "%s with %ld point%s,",
			how==ASCENDED ? "went to your reward" :
					"escaped from the dungeon",
			u.urexp, plur(u.urexp));
		putstr(endwin, 0, pbuf);
	    }

	    if (!done_stopprint)
		artifact_score(invent, FALSE, endwin);	/* list artifacts */

	    /* list valuables here */
	    for (val = valuables; val->list; val++) {
		sort_valuables(val->list, val->size);
		for (i = 0; i < val->size && !done_stopprint; i++) {
		    int typ = val->list[i].typ;
		    long count = val->list[i].count;

		    if (count == 0L) continue;
		    if (objects[typ].oc_class != GEM_CLASS || typ <= LAST_GEM) {
			otmp = mksobj(typ, FALSE, FALSE);
			makeknown(otmp->otyp);
			otmp->known = 1;	/* for fake amulets */
			otmp->dknown = 1;	/* seen it (blindness fix) */
			if (has_oname(otmp)) free_oname(otmp);
			otmp->quan = count;
			Sprintf(pbuf, "%8ld %s (worth %ld %s),",
				count, xname(otmp),
				count * (long)objects[typ].oc_cost, currency(2L));
			obfree(otmp, (struct obj *)0);
		    } else {
			Sprintf(pbuf,
				"%8ld worthless piece%s of colored glass,",
				count, plur(count));
		    }
		    putstr(endwin, 0, pbuf);
		}
	    }

	} else if (!done_stopprint) {
	    /* did not escape or ascend */
	    if (u.uz.dnum == 0 && u.uz.dlevel <= 0) {
		/* level teleported out of the dungeon; `how' is DIED,
		   due to falling or to "arriving at heaven prematurely" */
		Sprintf(pbuf, "You %s beyond the confines of the dungeon",
			(u.uz.dlevel < 0) ? "passed away" : ends[how]);
	    } else {
		/* more conventional demise */
		const char *where = dungeons[u.uz.dnum].dname;

		if (Is_astralevel(&u.uz)) where = "The Astral Plane";
		Sprintf(pbuf, "You %s in %s", ends[how], where);
		if (!In_endgame(&u.uz) && !Is_knox(&u.uz))
		    Sprintf(eos(pbuf), " on dungeon level %d",
			    In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz));
	    }

	    Sprintf(eos(pbuf), " with %ld point%s,",
		    u.urexp, plur(u.urexp));
	    putstr(endwin, 0, pbuf);
	}

	if (!done_stopprint) {
	    Sprintf(pbuf, "and %ld piece%s of gold, after %ld move%s.",
		    umoney, plur(umoney), moves, plur(moves));
	    putstr(endwin, 0, pbuf);
	}
	if (!done_stopprint) {
	    Sprintf(pbuf,
	     "You were level %d with a maximum of %d hit point%s when you %s.",
		    u.ulevel, u.uhpmax, plur(u.uhpmax), ends[how]);
	    putstr(endwin, 0, pbuf);
	    putstr(endwin, 0, "");
	}
	if (!done_stopprint)
	    display_nhwindow(endwin, TRUE);
	if (endwin != WIN_ERR)
	    destroy_nhwindow(endwin);

	/* "So when I die, the first thing I will see in Heaven is a
	 * score list?" */
	if (have_windows && !iflags.toptenwin)
	    exit_nhwindows((char *)0), have_windows = FALSE;
	topten(how, endtime);
	if (have_windows)
	    exit_nhwindows((char *)0);

	if(done_stopprint) { raw_print(""); raw_print(""); }
	terminate(EXIT_SUCCESS);
}


void
container_contents(list, identified, all_containers, reportempty)
struct obj *list;
boolean identified, all_containers, reportempty;
{
	register struct obj *box, *obj;
	char buf[BUFSZ];
	boolean cat, deadcat;

	for (box = list; box; box = box->nobj) {
	    if (Is_container(box) || box->otyp == STATUE) {
		box->cknown = 1;	/* we're looking at the contents now */
		if (identified) box->lknown = 1;
		cat = deadcat = FALSE;
		if (SchroedingersBox(box) && !Schroedingers_cat) {
			/* Schroedinger's Cat? */
			cat = odds_and_ends(box, CAT_CHECK);
			if (cat) Schroedingers_cat = TRUE;
			else deadcat = TRUE;
			box->spe = 0;
		}
		if (box->otyp == BAG_OF_TRICKS) {
		    continue;	/* wrong type of container */
		} else if (box->cobj) {
		    winid tmpwin = create_nhwindow(NHW_MENU);
		    Sprintf(buf, "Contents of %s:", the(xname(box)));
		    putstr(tmpwin, 0, buf);
		    putstr(tmpwin, 0, "");
		    for (obj = box->cobj; obj; obj = obj->nobj) {
			if (identified) {
			    makeknown(obj->otyp);
			    obj->known = obj->bknown =
			    obj->dknown = obj->rknown = 1;
			    if (Is_container(obj) || obj->otyp == STATUE)
				obj->cknown = obj->lknown = 1;
			}
			putstr(tmpwin, 0, doname(obj));
		    }
		    if (cat) putstr(tmpwin, 0, "Schroedinger's cat");
		    else if (deadcat) putstr(tmpwin, 0, "Schroedinger's dead cat");
		    display_nhwindow(tmpwin, TRUE);
		    destroy_nhwindow(tmpwin);
		    if (all_containers)
			container_contents(box->cobj, identified, TRUE,
					   reportempty);
		} else if (cat || deadcat) {
		    pline("%s Schroedinger's %scat!",
			  Tobjnam(box, "contain"), deadcat ? "dead " : "");
		    display_nhwindow(WIN_MESSAGE, FALSE);
		} else if (reportempty) {
		    pline("%s is empty.", upstart(thesimpleoname(box)));
		    display_nhwindow(WIN_MESSAGE, FALSE);
		}
	    }
	    if (!all_containers)
		break;
	}
}


/* should be called with either EXIT_SUCCESS or EXIT_FAILURE */
void
terminate(status)
int status;
{
	program_state.in_moveloop = 0;	/* won't be returning to normal play */
#ifdef MAC
	getreturn("to exit");
#endif
	/* don't bother to try to release memory if we're in panic mode, to
	   avoid trouble in case that happens to be due to memory problems */
	if (!program_state.panicking) {
	    freedynamicdata();
	    dlb_cleanup();
	}

#ifdef VMS
	/* don't call exit() if already executing within an exit handler;
	   that would cancel any other pending user-mode handlers */
	if (program_state.exiting) return;
#endif
	program_state.exiting = 1;
	nethack_exit(status);
}

/* #vanquished command */
int
dovanquished()
{
    list_vanquished('a', FALSE);
    return 0;
}

STATIC_OVL void
list_vanquished(defquery, ask)
char defquery;
boolean ask;
{
    register int i, lev;
    int ntypes = 0, max_lev = 0, nkilled;
    long total_killed = 0L;
    char c;
    winid klwin;
    char buf[BUFSZ];

    /* get totals first */
    for (i = LOW_PM; i < NUMMONS; i++) {
	if (mvitals[i].died) ntypes++;
	total_killed += (long)mvitals[i].died;
	if (mons[i].mlevel > max_lev) max_lev = mons[i].mlevel;
    }

    /* vanquished creatures list;
     * includes all dead monsters, not just those killed by the player
     */
    if (ntypes != 0) {
	c = ask ? yn_function("Do you want an account of creatures vanquished?",
			      ynqchars, defquery) : defquery;
	if (c == 'q') done_stopprint++;
	if (c == 'y' || c == 'a') {
	    klwin = create_nhwindow(NHW_MENU);
	    putstr(klwin, 0, "Vanquished creatures:");
	    putstr(klwin, 0, "");

	    /* countdown by monster "toughness" */
	    for (lev = max_lev; lev >= 0; lev--)
	      for (i = LOW_PM; i < NUMMONS; i++)
		if (mons[i].mlevel == lev && (nkilled = mvitals[i].died) > 0) {
		    if ((mons[i].geno & G_UNIQ) && i != PM_HIGH_PRIEST) {
			Sprintf(buf, "%s%s",
				!type_is_pname(&mons[i]) ? "The " : "",
				mons[i].mname);
			if (nkilled > 1) {
			    switch (nkilled) {
				case 2:  Sprintf(eos(buf)," (twice)");  break;
				case 3:  Sprintf(eos(buf)," (thrice)");  break;
				default: Sprintf(eos(buf)," (%d times)",
						 nkilled);
					 break;
			    }
			}
		    } else {
			/* trolls or undead might have come back,
			   but we don't keep track of that */
			if (nkilled == 1)
			    Strcpy(buf, an(mons[i].mname));
			else
			    Sprintf(buf, "%d %s",
				    nkilled, makeplural(mons[i].mname));
		    }
		    putstr(klwin, 0, buf);
		}
	    /*
	     * if (Hallucination)
	     *     putstr(klwin, 0, "and a partridge in a pear tree");
	     */
	    if (ntypes > 1) {
		putstr(klwin, 0, "");
		Sprintf(buf, "%ld creatures vanquished.", total_killed);
		putstr(klwin, 0, buf);
	    }
	    display_nhwindow(klwin, TRUE);
	    destroy_nhwindow(klwin);
	}
    } else if (defquery == 'a') {
	/* #dovanquished rather than final disclosure, so pline() is ok */
	pline("No monsters have been vanquished.");
    }
}

/* number of monster species which have been genocided */
int
num_genocides()
{
    int i, n = 0;

    for (i = LOW_PM; i < NUMMONS; ++i)
	if (mvitals[i].mvflags & G_GENOD) ++n;

    return n;
}

STATIC_OVL void
list_genocided(defquery, ask)
char defquery;
boolean ask;
{
    register int i;
    int ngenocided;
    char c;
    winid klwin;
    char buf[BUFSZ];

    ngenocided = num_genocides();

    /* genocided species list */
    if (ngenocided != 0) {
	c = ask ? yn_function("Do you want a list of species genocided?",
			      ynqchars, defquery) : defquery;
	if (c == 'q') done_stopprint++;
	if (c == 'y') {
	    klwin = create_nhwindow(NHW_MENU);
	    putstr(klwin, 0, "Genocided species:");
	    putstr(klwin, 0, "");

	    for (i = LOW_PM; i < NUMMONS; i++)
		if (mvitals[i].mvflags & G_GENOD) {
		    if ((mons[i].geno & G_UNIQ) && i != PM_HIGH_PRIEST)
			Sprintf(buf, "%s%s",
				!type_is_pname(&mons[i]) ? "" : "the ",
				mons[i].mname);
		    else
			Strcpy(buf, makeplural(mons[i].mname));
		    putstr(klwin, 0, buf);
		}

	    putstr(klwin, 0, "");
	    Sprintf(buf, "%d species genocided.", ngenocided);
	    putstr(klwin, 0, buf);

	    display_nhwindow(klwin, TRUE);
	    destroy_nhwindow(klwin);
	}
    }
}

/* set a delayed killer, ensure non-delayed killer is cleared out */
void
delayed_killer(id, format, killername)
    int		id;
    int		format;
    const char	*killername;
{
    struct kinfo *k = find_delayed_killer(id);

    if (k == (struct kinfo*) 0) {
	/* no match, add a new delayed killer to the list */
	k = (struct kinfo*) alloc(sizeof(struct kinfo));
	k->id = id;
	k->next = killer.next;
	killer.next = k;
    }

    k->format = format;
    Strcpy(k->name, killername ? killername : "");
    killer.name[0] = 0;
}

struct kinfo*
find_delayed_killer(id)
    int id;
{
    struct kinfo* k;

    for (k = killer.next; k != (struct kinfo*) 0; k = k->next) {
	if (k->id == id) break;
    }

    return k;
}

void
dealloc_killer(kptr)
    struct kinfo *kptr;
{
    struct kinfo *prev = &killer, *k;

    if (kptr == (struct kinfo *)0) return;
    for (k = killer.next; k != (struct kinfo*) 0; k = k->next) {
	if (k == kptr) break;
	prev = k;
    }

    if (k == (struct kinfo*) 0) {
	impossible("dealloc_killer not on list");
    } else {
	prev->next = k->next;
	free((genericptr_t) k);
    }
}

void
save_killers(fd, mode)
    int fd;
    int mode;
{
    struct kinfo *kptr;

    if (perform_bwrite(mode)) {
	for (kptr = &killer; kptr != (struct kinfo*)0; kptr = kptr->next) {
	    bwrite(fd, (genericptr_t)kptr, sizeof(struct kinfo));
	}
    }
    if (release_data(mode)) {
	while (killer.next) {
	    kptr = killer.next->next;
	    free((genericptr_t)killer.next);
	    killer.next = kptr;
	}
    }
}

void
restore_killers(fd)
    int fd;
{
    struct kinfo *kptr;

    for (kptr = &killer; kptr != (struct kinfo*)0; kptr = kptr->next) {
	mread(fd, (genericptr_t)kptr, sizeof(struct kinfo));
	if (kptr->next) {
	    kptr->next = (struct kinfo*) alloc(sizeof(struct kinfo));
	}
    }
}

static int
wordcount(p)
    char *p;
{
    int words = 0;
    while(*p){
	while(*p && isspace(*p))p++;
	if(*p) words++;
	while(*p && !isspace(*p))p++;
    }
    return words;
}

static void
bel_copy1(inp, out)
    char **inp, *out;
{
    char *in = *inp;

    out += strlen(out);	/* eos() */
    while(*in && isspace(*in)) in++;
    while(*in && !isspace(*in)) *out++ = *in++;
    *out = '\0';
    *inp = in;
}

char *
build_english_list(in)
    char *in;
{
    char *out, *p = in;
    int len = (int)strlen(p), words = wordcount(p);

    /* +3: " or " - " "; +(words - 1): (N-1)*(", " - " ") */
    if (words > 1) len += 3 + (words - 1);
    out = (char *)alloc(len + 1);
    *out = '\0';	/* bel_copy1() appends */

    switch(words){
    case 0:
	impossible("no words in list");
	break;
    case 1:
	/* "single" */
	bel_copy1(&p, out);
	break;
    default:
	if (words == 2) {
	    /* "first or second" */
	    bel_copy1(&p, out);
	    Strcat(out, " ");
	} else {
	    /* "first, second, or third */
	    do {
		bel_copy1(&p, out);
		Strcat(out, ", ");
	    } while (--words > 1);
	}
	Strcat(out, "or ");
	bel_copy1(&p, out);
	break;
    }
    return out;
}

/*end.c*/
