miscellaneous formatting
[aNetHack.git] / src / end.c
blob817ca411120805c9139c36a911e408dcbd775846
1 /* NetHack 3.6 end.c $NHDT-Date: 1488075979 2017/02/26 02:26:19 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.127 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 #define NEED_VARARGS /* comment line for pre-compiled headers */
7 #include "hack.h"
8 #include "lev.h"
9 #ifndef NO_SIGNAL
10 #include <signal.h>
11 #endif
12 #include <ctype.h>
13 #include <limits.h>
14 #include "dlb.h"
16 /* add b to long a, convert wraparound to max value */
17 #define nowrap_add(a, b) (a = ((a + b) < 0 ? LONG_MAX : (a + b)))
19 /* these probably ought to be generated by makedefs, like LAST_GEM */
20 #define FIRST_GEM DILITHIUM_CRYSTAL
21 #define FIRST_AMULET AMULET_OF_ESP
22 #define LAST_AMULET AMULET_OF_YENDOR
24 struct valuable_data {
25 long count;
26 int typ;
29 static struct valuable_data
30 gems[LAST_GEM + 1 - FIRST_GEM + 1], /* 1 extra for glass */
31 amulets[LAST_AMULET + 1 - FIRST_AMULET];
33 static struct val_list {
34 struct valuable_data *list;
35 int size;
36 } valuables[] = { { gems, sizeof gems / sizeof *gems },
37 { amulets, sizeof amulets / sizeof *amulets },
38 { 0, 0 } };
40 #ifndef NO_SIGNAL
41 STATIC_PTR void FDECL(done_intr, (int));
42 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
43 static void FDECL(done_hangup, (int));
44 #endif
45 #endif
46 STATIC_DCL void FDECL(disclose, (int, BOOLEAN_P));
47 STATIC_DCL void FDECL(get_valuables, (struct obj *));
48 STATIC_DCL void FDECL(sort_valuables, (struct valuable_data *, int));
49 STATIC_DCL void FDECL(artifact_score, (struct obj *, BOOLEAN_P, winid));
50 STATIC_DCL void FDECL(really_done, (int)) NORETURN;
51 STATIC_DCL boolean FDECL(odds_and_ends, (struct obj *, int));
52 STATIC_DCL void FDECL(savelife, (int));
53 STATIC_PTR int FDECL(CFDECLSPEC vanqsort_cmp, (const genericptr,
54 const genericptr));
55 STATIC_DCL int NDECL(set_vanq_order);
56 STATIC_DCL void FDECL(list_vanquished, (CHAR_P, BOOLEAN_P));
57 STATIC_DCL void FDECL(list_genocided, (CHAR_P, BOOLEAN_P));
58 STATIC_DCL boolean FDECL(should_query_disclose_option, (int, char *));
59 #ifdef DUMPLOG
60 STATIC_DCL void NDECL(dump_plines);
61 #endif
62 STATIC_DCL void FDECL(dump_everything, (int));
63 STATIC_DCL int NDECL(num_extinct);
65 #if defined(__BEOS__) || defined(MICRO) || defined(WIN32) || defined(OS2)
66 extern void FDECL(nethack_exit, (int));
67 #else
68 #define nethack_exit exit
69 #endif
71 #define done_stopprint program_state.stopprint
73 #ifndef PANICTRACE
74 #define NH_abort NH_abort_
75 #endif
77 #ifdef AMIGA
78 #define NH_abort_() Abort(0)
79 #else
80 #ifdef SYSV
81 #define NH_abort_() (void) abort()
82 #else
83 #ifdef WIN32
84 #define NH_abort_() win32_abort()
85 #else
86 #define NH_abort_() abort()
87 #endif
88 #endif /* !SYSV */
89 #endif /* !AMIGA */
91 #ifdef PANICTRACE
92 #include <errno.h>
93 #ifdef PANICTRACE_LIBC
94 #include <execinfo.h>
95 #endif
97 /* What do we try and in what order? Tradeoffs:
98 * libc: +no external programs required
99 * -requires newish libc/glibc
100 * -requires -rdynamic
101 * gdb: +gives more detailed information
102 * +works on more OS versions
103 * -requires -g, which may preclude -O on some compilers
105 #ifdef SYSCF
106 #define SYSOPT_PANICTRACE_GDB sysopt.panictrace_gdb
107 #ifdef PANICTRACE_LIBC
108 #define SYSOPT_PANICTRACE_LIBC sysopt.panictrace_libc
109 #else
110 #define SYSOPT_PANICTRACE_LIBC 0
111 #endif
112 #else
113 #define SYSOPT_PANICTRACE_GDB (nh_getenv("NETHACK_USE_GDB") == 0 ? 0 : 2)
114 #ifdef PANICTRACE_LIBC
115 #define SYSOPT_PANICTRACE_LIBC 1
116 #else
117 #define SYSOPT_PANICTRACE_LIBC 0
118 #endif
119 #endif
121 static void NDECL(NH_abort);
122 #ifndef NO_SIGNAL
123 static void FDECL(panictrace_handler, (int));
124 #endif
125 static boolean NDECL(NH_panictrace_libc);
126 static boolean NDECL(NH_panictrace_gdb);
128 #ifndef NO_SIGNAL
129 /*ARGSUSED*/
130 void panictrace_handler(
131 sig_unused) /* called as signal() handler, so sent at least one arg */
132 int sig_unused UNUSED;
134 #define SIG_MSG "\nSignal received.\n"
135 (void) write(2, SIG_MSG, sizeof(SIG_MSG) - 1);
136 NH_abort();
139 void
140 panictrace_setsignals(set)
141 boolean set;
143 #define SETSIGNAL(sig) \
144 (void) signal(sig, set ? (SIG_RET_TYPE) panictrace_handler : SIG_DFL);
145 #ifdef SIGILL
146 SETSIGNAL(SIGILL);
147 #endif
148 #ifdef SIGTRAP
149 SETSIGNAL(SIGTRAP);
150 #endif
151 #ifdef SIGIOT
152 SETSIGNAL(SIGIOT);
153 #endif
154 #ifdef SIGBUS
155 SETSIGNAL(SIGBUS);
156 #endif
157 #ifdef SIGFPE
158 SETSIGNAL(SIGFPE);
159 #endif
160 #ifdef SIGSEGV
161 SETSIGNAL(SIGSEGV);
162 #endif
163 #ifdef SIGSTKFLT
164 SETSIGNAL(SIGSTKFLT);
165 #endif
166 #ifdef SIGSYS
167 SETSIGNAL(SIGSYS);
168 #endif
169 #ifdef SIGEMT
170 SETSIGNAL(SIGEMT);
171 #endif
172 #undef SETSIGNAL
174 #endif /* NO_SIGNAL */
176 static void
177 NH_abort()
179 int gdb_prio = SYSOPT_PANICTRACE_GDB;
180 int libc_prio = SYSOPT_PANICTRACE_LIBC;
181 static boolean aborting = FALSE;
183 if (aborting)
184 return;
185 aborting = TRUE;
187 #ifndef VMS
188 if (gdb_prio == libc_prio && gdb_prio > 0)
189 gdb_prio++;
191 if (gdb_prio > libc_prio) {
192 (void) (NH_panictrace_gdb() || (libc_prio && NH_panictrace_libc()));
193 } else {
194 (void) (NH_panictrace_libc() || (gdb_prio && NH_panictrace_gdb()));
197 #else /* VMS */
198 /* overload otherwise unused priority for debug mode: 1 = show
199 traceback and exit; 2 = show traceback and stay in debugger */
200 /* if (wizard && gdb_prio == 1) gdb_prio = 2; */
201 vms_traceback(gdb_prio);
202 (void) libc_prio; /* half-hearted attempt at lint suppression */
204 #endif /* ?VMS */
206 #ifndef NO_SIGNAL
207 panictrace_setsignals(FALSE);
208 #endif
209 NH_abort_();
212 static boolean
213 NH_panictrace_libc()
215 #ifdef PANICTRACE_LIBC
216 void *bt[20];
217 size_t count, x;
218 char **info;
220 raw_print("Generating more information you may report:\n");
221 count = backtrace(bt, SIZE(bt));
222 info = backtrace_symbols(bt, count);
223 for (x = 0; x < count; x++) {
224 raw_printf("[%lu] %s", (unsigned long) x, info[x]);
226 /* free(info); -- Don't risk it. */
227 return TRUE;
228 #else
229 return FALSE;
230 #endif /* !PANICTRACE_LIBC */
234 * fooPATH file system path for foo
235 * fooVAR (possibly const) variable containing fooPATH
237 #ifdef PANICTRACE_GDB
238 #ifdef SYSCF
239 #define GDBVAR sysopt.gdbpath
240 #define GREPVAR sysopt.greppath
241 #else /* SYSCF */
242 #define GDBVAR GDBPATH
243 #define GREPVAR GREPPATH
244 #endif /* SYSCF */
245 #endif /* PANICTRACE_GDB */
247 static boolean
248 NH_panictrace_gdb()
250 #ifdef PANICTRACE_GDB
251 /* A (more) generic method to get a stack trace - invoke
252 * gdb on ourself. */
253 char *gdbpath = GDBVAR;
254 char *greppath = GREPVAR;
255 char buf[BUFSZ];
256 FILE *gdb;
258 if (gdbpath == NULL || gdbpath[0] == 0)
259 return FALSE;
260 if (greppath == NULL || greppath[0] == 0)
261 return FALSE;
263 sprintf(buf, "%s -n -q %s %d 2>&1 | %s '^#'", gdbpath, ARGV0, getpid(),
264 greppath);
265 gdb = popen(buf, "w");
266 if (gdb) {
267 raw_print("Generating more information you may report:\n");
268 fprintf(gdb, "bt\nquit\ny");
269 fflush(gdb);
270 sleep(4); /* ugly */
271 pclose(gdb);
272 return TRUE;
273 } else {
274 return FALSE;
276 #else
277 return FALSE;
278 #endif /* !PANICTRACE_GDB */
280 #endif /* PANICTRACE */
283 * The order of these needs to match the macros in hack.h.
285 static NEARDATA const char *deaths[] = {
286 /* the array of death */
287 "died", "choked", "poisoned", "starvation", "drowning", "burning",
288 "dissolving under the heat and pressure", "crushed", "turned to stone",
289 "turned into slime", "genocided", "panic", "trickery", "quit",
290 "escaped", "ascended"
293 static NEARDATA const char *ends[] = {
294 /* "when you %s" */
295 "died", "choked", "were poisoned",
296 "starved", "drowned", "burned",
297 "dissolved in the lava",
298 "were crushed", "turned to stone",
299 "turned into slime", "were genocided",
300 "panicked", "were tricked", "quit",
301 "escaped", "ascended"
304 static boolean Schroedingers_cat = FALSE;
306 /*ARGSUSED*/
307 void
308 done1(sig_unused) /* called as signal() handler, so sent at least one arg */
309 int sig_unused UNUSED;
311 #ifndef NO_SIGNAL
312 (void) signal(SIGINT, SIG_IGN);
313 #endif
314 if (flags.ignintr) {
315 #ifndef NO_SIGNAL
316 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
317 #endif
318 clear_nhwindow(WIN_MESSAGE);
319 curs_on_u();
320 wait_synch();
321 if (multi > 0)
322 nomul(0);
323 } else {
324 (void) done2();
328 /* "#quit" command or keyboard interrupt */
330 done2()
332 if (!paranoid_query(ParanoidQuit, "Really quit?")) {
333 #ifndef NO_SIGNAL
334 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
335 #endif
336 clear_nhwindow(WIN_MESSAGE);
337 curs_on_u();
338 wait_synch();
339 if (multi > 0)
340 nomul(0);
341 if (multi == 0) {
342 u.uinvulnerable = FALSE; /* avoid ctrl-C bug -dlc */
343 u.usleep = 0;
345 return 0;
347 #if (defined(UNIX) || defined(VMS) || defined(LATTICE))
348 if (wizard) {
349 int c;
350 #ifdef VMS
351 extern int debuggable; /* sys/vms/vmsmisc.c, vmsunix.c */
353 c = !debuggable ? 'n' : ynq("Enter debugger?");
354 #else
355 #ifdef LATTICE
356 c = ynq("Create SnapShot?");
357 #else
358 c = ynq("Dump core?");
359 #endif
360 #endif
361 if (c == 'y') {
362 #ifndef NO_SIGNAL
363 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
364 #endif
365 exit_nhwindows((char *) 0);
366 NH_abort();
367 } else if (c == 'q')
368 done_stopprint++;
370 #endif
371 #ifndef LINT
372 done(QUIT);
373 #endif
374 return 0;
377 #ifndef NO_SIGNAL
378 /*ARGSUSED*/
379 STATIC_PTR void
380 done_intr(sig_unused) /* called as signal() handler, so sent at least 1 arg */
381 int sig_unused UNUSED;
383 done_stopprint++;
384 (void) signal(SIGINT, SIG_IGN);
385 #if defined(UNIX) || defined(VMS)
386 (void) signal(SIGQUIT, SIG_IGN);
387 #endif
388 return;
391 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
392 /* signal() handler */
393 static void
394 done_hangup(sig)
395 int sig;
397 program_state.done_hup++;
398 sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
399 done_intr(sig);
400 return;
402 #endif
403 #endif /* NO_SIGNAL */
405 void
406 done_in_by(mtmp, how)
407 struct monst *mtmp;
408 int how;
410 char buf[BUFSZ];
411 struct permonst *mptr = mtmp->data,
412 *champtr = ((mtmp->cham >= LOW_PM)
413 ? &mons[mtmp->cham]
414 : mptr);
415 boolean distorted = (boolean) (Hallucination && canspotmon(mtmp)),
416 mimicker = (mtmp->m_ap_type == M_AP_MONSTER),
417 imitator = (mptr != champtr || mimicker);
419 You((how == STONING) ? "turn to stone..." : "die...");
420 mark_synch(); /* flush buffered screen output */
421 buf[0] = '\0';
422 killer.format = KILLED_BY_AN;
423 /* "killed by the high priest of Crom" is okay,
424 "killed by the high priest" alone isn't */
425 if ((mptr->geno & G_UNIQ) != 0 && !(imitator && !mimicker)
426 && !(mptr == &mons[PM_HIGH_PRIEST] && !mtmp->ispriest)) {
427 if (!type_is_pname(mptr))
428 Strcat(buf, "the ");
429 killer.format = KILLED_BY;
431 /* _the_ <invisible> <distorted> ghost of Dudley */
432 if (mptr == &mons[PM_GHOST] && has_mname(mtmp)) {
433 Strcat(buf, "the ");
434 killer.format = KILLED_BY;
436 if (mtmp->minvis)
437 Strcat(buf, "invisible ");
438 if (distorted)
439 Strcat(buf, "hallucinogen-distorted ");
441 if (imitator) {
442 char shape[BUFSZ];
443 const char *realnm = champtr->mname, *fakenm = mptr->mname;
444 boolean alt = is_vampshifter(mtmp);
446 if (mimicker) {
447 /* realnm is already correct because champtr==mptr;
448 set up fake mptr for type_is_pname/the_unique_pm */
449 mptr = &mons[mtmp->mappearance];
450 fakenm = mptr->mname;
451 } else if (alt && strstri(realnm, "vampire")
452 && !strcmp(fakenm, "vampire bat")) {
453 /* special case: use "vampire in bat form" in preference
454 to redundant looking "vampire in vampire bat form" */
455 fakenm = "bat";
457 /* for the alternate format, always suppress any article;
458 pname and the_unique should also have s_suffix() applied,
459 but vampires don't take on any shapes which warrant that */
460 if (alt || type_is_pname(mptr)) /* no article */
461 Strcpy(shape, fakenm);
462 else if (the_unique_pm(mptr)) /* "the"; don't use the() here */
463 Sprintf(shape, "the %s", fakenm);
464 else /* "a"/"an" */
465 Strcpy(shape, an(fakenm));
466 /* omit "called" to avoid excessive verbosity */
467 Sprintf(eos(buf),
468 alt ? "%s in %s form"
469 : mimicker ? "%s disguised as %s"
470 : "%s imitating %s",
471 realnm, shape);
472 mptr = mtmp->data; /* reset for mimicker case */
473 } else if (mptr == &mons[PM_GHOST]) {
474 Strcat(buf, "ghost");
475 if (has_mname(mtmp))
476 Sprintf(eos(buf), " of %s", MNAME(mtmp));
477 } else if (mtmp->isshk) {
478 const char *shknm = shkname(mtmp),
479 *honorific = shkname_is_pname(mtmp) ? ""
480 : mtmp->female ? "Ms. " : "Mr. ";
482 Sprintf(eos(buf), "%s%s, the shopkeeper", honorific, shknm);
483 killer.format = KILLED_BY;
484 } else if (mtmp->ispriest || mtmp->isminion) {
485 /* m_monnam() suppresses "the" prefix plus "invisible", and
486 it overrides the effect of Hallucination on priestname() */
487 Strcat(buf, m_monnam(mtmp));
488 } else {
489 Strcat(buf, mptr->mname);
490 if (has_mname(mtmp))
491 Sprintf(eos(buf), " called %s", MNAME(mtmp));
494 Strcpy(killer.name, buf);
495 if (mptr->mlet == S_WRAITH)
496 u.ugrave_arise = PM_WRAITH;
497 else if (mptr->mlet == S_MUMMY && urace.mummynum != NON_PM)
498 u.ugrave_arise = urace.mummynum;
499 else if (mptr->mlet == S_VAMPIRE && Race_if(PM_HUMAN))
500 u.ugrave_arise = PM_VAMPIRE;
501 else if (mptr == &mons[PM_GHOUL])
502 u.ugrave_arise = PM_GHOUL;
503 /* this could happen if a high-end vampire kills the hero
504 when ordinary vampires are genocided; ditto for wraiths */
505 if (u.ugrave_arise >= LOW_PM
506 && (mvitals[u.ugrave_arise].mvflags & G_GENOD))
507 u.ugrave_arise = NON_PM;
509 done(how);
510 return;
513 /* some special cases for overriding while-helpless reason */
514 static const struct {
515 int why, unmulti;
516 const char *exclude, *include;
517 } death_fixups[] = {
518 /* "petrified by <foo>, while getting stoned" -- "while getting stoned"
519 prevented any last-second recovery, but it was not the cause of
520 "petrified by <foo>" */
521 { STONING, 1, "getting stoned", (char *) 0 },
522 /* "died of starvation, while fainted from lack of food" is accurate
523 but sounds a fairly silly (and doesn't actually appear unless you
524 splice together death and while-helpless from xlogfile) */
525 { STARVING, 0, "fainted from lack of food", "fainted" },
528 /* clear away while-helpless when the cause of death caused that
529 helplessness (ie, "petrified by <foo> while getting stoned") */
530 STATIC_DCL void
531 fixup_death(how)
532 int how;
534 int i;
536 if (multi_reason) {
537 for (i = 0; i < SIZE(death_fixups); ++i)
538 if (death_fixups[i].why == how
539 && !strcmp(death_fixups[i].exclude, multi_reason)) {
540 if (death_fixups[i].include) /* substitute alternate reason */
541 multi_reason = death_fixups[i].include;
542 else /* remove the helplessness reason */
543 multi_reason = (char *) 0;
544 if (death_fixups[i].unmulti) /* possibly hide helplessness */
545 multi = 0L;
546 break;
551 #if defined(WIN32) && !defined(SYSCF)
552 #define NOTIFY_NETHACK_BUGS
553 #endif
555 /*VARARGS1*/
556 void panic
557 VA_DECL(const char *, str)
559 VA_START(str);
560 VA_INIT(str, char *);
562 if (program_state.panicking++)
563 NH_abort(); /* avoid loops - this should never happen*/
565 if (iflags.window_inited) {
566 raw_print("\r\nOops...");
567 wait_synch(); /* make sure all pending output gets flushed */
568 exit_nhwindows((char *) 0);
569 iflags.window_inited = 0; /* they're gone; force raw_print()ing */
572 raw_print(program_state.gameover
573 ? "Postgame wrapup disrupted."
574 : !program_state.something_worth_saving
575 ? "Program initialization has failed."
576 : "Suddenly, the dungeon collapses.");
577 #ifndef MICRO
578 #if defined(NOTIFY_NETHACK_BUGS)
579 if (!wizard)
580 raw_printf("Report the following error to \"%s\" or at \"%s\".",
581 DEVTEAM_EMAIL, DEVTEAM_URL);
582 else if (program_state.something_worth_saving)
583 raw_print("\nError save file being written.\n");
584 #else
585 if (!wizard) {
586 const char *maybe_rebuild = !program_state.something_worth_saving
587 ? "."
588 : "\nand it may be possible to rebuild.";
590 if (sysopt.support)
591 raw_printf("To report this error, %s%s", sysopt.support,
592 maybe_rebuild);
593 else if (sysopt.fmtd_wizard_list) /* formatted SYSCF WIZARDS */
594 raw_printf("To report this error, contact %s%s",
595 sysopt.fmtd_wizard_list, maybe_rebuild);
596 else
597 raw_printf("Report error to \"%s\"%s", WIZARD_NAME,
598 maybe_rebuild);
600 #endif
601 /* XXX can we move this above the prints? Then we'd be able to
602 * suppress "it may be possible to rebuild" based on dosave0()
603 * or say it's NOT possible to rebuild. */
604 if (program_state.something_worth_saving) {
605 set_error_savefile();
606 if (dosave0()) {
607 /* os/win port specific recover instructions */
608 if (sysopt.recover)
609 raw_printf("%s", sysopt.recover);
612 #endif
614 char buf[BUFSZ];
616 Vsprintf(buf, str, VA_ARGS);
617 raw_print(buf);
618 paniclog("panic", buf);
620 #ifdef WIN32
621 interject(INTERJECT_PANIC);
622 #endif
623 #if defined(UNIX) || defined(VMS) || defined(LATTICE) || defined(WIN32)
624 if (wizard)
625 NH_abort(); /* generate core dump */
626 #endif
627 VA_END();
628 really_done(PANICKED);
631 STATIC_OVL boolean
632 should_query_disclose_option(category, defquery)
633 int category;
634 char *defquery;
636 int idx;
637 char disclose, *dop;
639 *defquery = 'n';
640 if ((dop = index(disclosure_options, category)) != 0) {
641 idx = (int) (dop - disclosure_options);
642 if (idx < 0 || idx >= NUM_DISCLOSURE_OPTIONS) {
643 impossible(
644 "should_query_disclose_option: bad disclosure index %d %c",
645 idx, category);
646 *defquery = DISCLOSE_PROMPT_DEFAULT_YES;
647 return TRUE;
649 disclose = flags.end_disclose[idx];
650 if (disclose == DISCLOSE_YES_WITHOUT_PROMPT) {
651 *defquery = 'y';
652 return FALSE;
653 } else if (disclose == DISCLOSE_SPECIAL_WITHOUT_PROMPT) {
654 *defquery = 'a';
655 return FALSE;
656 } else if (disclose == DISCLOSE_NO_WITHOUT_PROMPT) {
657 *defquery = 'n';
658 return FALSE;
659 } else if (disclose == DISCLOSE_PROMPT_DEFAULT_YES) {
660 *defquery = 'y';
661 return TRUE;
662 } else if (disclose == DISCLOSE_PROMPT_DEFAULT_SPECIAL) {
663 *defquery = 'a';
664 return TRUE;
665 } else {
666 *defquery = 'n';
667 return TRUE;
670 impossible("should_query_disclose_option: bad category %c", category);
671 return TRUE;
674 #ifdef DUMPLOG
675 STATIC_OVL void
676 dump_plines()
678 int i;
679 char buf[BUFSZ], **strp;
680 extern char *saved_plines[];
682 Strcpy(buf, " ");
683 putstr(0, 0, "");
684 putstr(0, 0, "Latest messages:");
685 for (i = 0; i < DUMPLOG_MSG_COUNT; ++i) {
686 strp = &saved_plines[DUMPLOG_MSG_COUNT - 1 - i];
687 if (*strp) {
688 copynchars(&buf[1], *strp, BUFSZ - 1 - 1);
689 putstr(0, 0, buf);
690 #ifdef FREE_ALL_MEMORY
691 free(*strp), *strp = 0;
692 #endif
696 #endif
698 STATIC_OVL void
699 dump_everything(how)
700 int how;
702 #ifdef DUMPLOG
703 struct obj *obj;
704 char pbuf[BUFSZ];
706 dump_redirect(TRUE);
707 if (!iflags.in_dumplog)
708 return;
710 init_symbols();
712 for (obj = invent; obj; obj = obj->nobj) {
713 makeknown(obj->otyp);
714 obj->known = obj->bknown = obj->dknown = obj->rknown = 1;
715 if (Is_container(obj) || obj->otyp == STATUE)
716 obj->cknown = obj->lknown = 1;
719 Sprintf(pbuf, "%s, %s %s %s %s", plname,
720 aligns[1 - u.ualign.type].adj,
721 genders[flags.female].adj,
722 urace.adj,
723 (flags.female && urole.name.f) ? urole.name.f : urole.name.m);
724 putstr(0, 0, pbuf);
725 putstr(0, 0, "");
727 dump_map();
728 putstr(0, 0, do_statusline1());
729 putstr(0, 0, do_statusline2());
730 putstr(0, 0, "");
732 dump_plines();
733 putstr(0, 0, "");
734 putstr(0, 0, "Inventory:");
735 display_inventory((char *) 0, TRUE);
736 container_contents(invent, TRUE, TRUE, FALSE);
737 enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT),
738 (how >= PANICKED) ? ENL_GAMEOVERALIVE
739 : ENL_GAMEOVERDEAD);
740 putstr(0, 0, "");
741 list_vanquished('y', FALSE);
742 putstr(0, 0, "");
743 list_genocided('a', FALSE);
744 putstr(0, 0, "");
745 show_conduct((how >= PANICKED) ? 1 : 2);
746 putstr(0, 0, "");
747 show_overview((how >= PANICKED) ? 1 : 2, how);
748 putstr(0, 0, "");
749 dump_redirect(FALSE);
750 #else
751 nhUse(how);
752 #endif
755 STATIC_OVL void
756 disclose(how, taken)
757 int how;
758 boolean taken;
760 char c = '\0', defquery;
761 char qbuf[QBUFSZ];
762 boolean ask = FALSE;
764 if (invent && !done_stopprint) {
765 if (taken)
766 Sprintf(qbuf, "Do you want to see what you had when you %s?",
767 (how == QUIT) ? "quit" : "died");
768 else
769 Strcpy(qbuf, "Do you want your possessions identified?");
771 ask = should_query_disclose_option('i', &defquery);
772 c = ask ? yn_function(qbuf, ynqchars, defquery) : defquery;
773 if (c == 'y') {
774 struct obj *obj;
776 for (obj = invent; obj; obj = obj->nobj) {
777 makeknown(obj->otyp);
778 obj->known = obj->bknown = obj->dknown = obj->rknown = 1;
779 if (Is_container(obj) || obj->otyp == STATUE)
780 obj->cknown = obj->lknown = 1;
782 (void) display_inventory((char *) 0, TRUE);
783 container_contents(invent, TRUE, TRUE, FALSE);
785 if (c == 'q')
786 done_stopprint++;
789 if (!done_stopprint) {
790 ask = should_query_disclose_option('a', &defquery);
791 c = ask ? yn_function("Do you want to see your attributes?", ynqchars,
792 defquery)
793 : defquery;
794 if (c == 'y')
795 enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT),
796 (how >= PANICKED) ? ENL_GAMEOVERALIVE
797 : ENL_GAMEOVERDEAD);
798 if (c == 'q')
799 done_stopprint++;
802 if (!done_stopprint) {
803 ask = should_query_disclose_option('v', &defquery);
804 list_vanquished(defquery, ask);
807 if (!done_stopprint) {
808 ask = should_query_disclose_option('g', &defquery);
809 list_genocided(defquery, ask);
812 if (!done_stopprint) {
813 ask = should_query_disclose_option('c', &defquery);
814 c = ask ? yn_function("Do you want to see your conduct?", ynqchars,
815 defquery)
816 : defquery;
817 if (c == 'y')
818 show_conduct((how >= PANICKED) ? 1 : 2);
819 if (c == 'q')
820 done_stopprint++;
823 if (!done_stopprint) {
824 ask = should_query_disclose_option('o', &defquery);
825 c = ask ? yn_function("Do you want to see the dungeon overview?",
826 ynqchars, defquery)
827 : defquery;
828 if (c == 'y')
829 show_overview((how >= PANICKED) ? 1 : 2, how);
830 if (c == 'q')
831 done_stopprint++;
835 /* try to get the player back in a viable state after being killed */
836 STATIC_OVL void
837 savelife(how)
838 int how;
840 int uhpmin = max(2 * u.ulevel, 10);
842 if (u.uhpmax < uhpmin)
843 u.uhpmax = uhpmin;
844 u.uhp = u.uhpmax;
845 u.uswldtim = 0;
846 if (u.uhunger < 500) {
847 u.uhunger = 500;
848 newuhs(FALSE);
850 /* cure impending doom of sickness hero won't have time to fix */
851 if ((Sick & TIMEOUT) == 1L) {
852 u.usick_type = 0;
853 set_itimeout(&Sick, 0L);
855 if (how == CHOKING)
856 init_uhunger();
857 nomovemsg = "You survived that attempt on your life.";
858 context.move = 0;
859 if (multi > 0)
860 multi = 0;
861 else
862 multi = -1;
863 if (u.utrap && u.utraptype == TT_LAVA)
864 u.utrap = 0;
865 context.botl = 1;
866 u.ugrave_arise = NON_PM;
867 HUnchanging = 0L;
868 curs_on_u();
869 if (!context.mon_moving)
870 endmultishot(FALSE);
874 * Get valuables from the given list. Revised code: the list always remains
875 * intact.
877 STATIC_OVL void
878 get_valuables(list)
879 struct obj *list; /* inventory or container contents */
881 register struct obj *obj;
882 register int i;
884 /* find amulets and gems, ignoring all artifacts */
885 for (obj = list; obj; obj = obj->nobj)
886 if (Has_contents(obj)) {
887 get_valuables(obj->cobj);
888 } else if (obj->oartifact) {
889 continue;
890 } else if (obj->oclass == AMULET_CLASS) {
891 i = obj->otyp - FIRST_AMULET;
892 if (!amulets[i].count) {
893 amulets[i].count = obj->quan;
894 amulets[i].typ = obj->otyp;
895 } else
896 amulets[i].count += obj->quan; /* always adds one */
897 } else if (obj->oclass == GEM_CLASS && obj->otyp < LUCKSTONE) {
898 i = min(obj->otyp, LAST_GEM + 1) - FIRST_GEM;
899 if (!gems[i].count) {
900 gems[i].count = obj->quan;
901 gems[i].typ = obj->otyp;
902 } else
903 gems[i].count += obj->quan;
905 return;
909 * Sort collected valuables, most frequent to least. We could just
910 * as easily use qsort, but we don't care about efficiency here.
912 STATIC_OVL void
913 sort_valuables(list, size)
914 struct valuable_data list[];
915 int size; /* max value is less than 20 */
917 register int i, j;
918 struct valuable_data ltmp;
920 /* move greater quantities to the front of the list */
921 for (i = 1; i < size; i++) {
922 if (list[i].count == 0)
923 continue; /* empty slot */
924 ltmp = list[i]; /* structure copy */
925 for (j = i; j > 0; --j)
926 if (list[j - 1].count >= ltmp.count)
927 break;
928 else {
929 list[j] = list[j - 1];
931 list[j] = ltmp;
933 return;
936 #define CAT_CHECK 2
938 STATIC_OVL boolean
939 odds_and_ends(list, what)
940 struct obj *list;
941 int what;
943 struct obj *otmp;
944 for (otmp = list; otmp; otmp = otmp->nobj) {
945 switch (what) {
946 case CAT_CHECK: /* Schroedinger's Cat */
947 /* Ascending is deterministic */
948 if (SchroedingersBox(otmp))
949 return rn2(2);
950 break;
952 if (Has_contents(otmp))
953 return odds_and_ends(otmp->cobj, what);
955 return FALSE;
958 /* called twice; first to calculate total, then to list relevant items */
959 STATIC_OVL void
960 artifact_score(list, counting, endwin)
961 struct obj *list;
962 boolean counting; /* true => add up points; false => display them */
963 winid endwin;
965 char pbuf[BUFSZ];
966 struct obj *otmp;
967 long value, points;
968 short dummy; /* object type returned by artifact_name() */
970 for (otmp = list; otmp; otmp = otmp->nobj) {
971 if (otmp->oartifact || otmp->otyp == BELL_OF_OPENING
972 || otmp->otyp == SPE_BOOK_OF_THE_DEAD
973 || otmp->otyp == CANDELABRUM_OF_INVOCATION) {
974 value = arti_cost(otmp); /* zorkmid value */
975 points = value * 5 / 2; /* score value */
976 if (counting) {
977 nowrap_add(u.urexp, points);
978 } else {
979 makeknown(otmp->otyp);
980 otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1;
981 /* assumes artifacts don't have quan > 1 */
982 Sprintf(pbuf, "%s%s (worth %ld %s and %ld points)",
983 the_unique_obj(otmp) ? "The " : "",
984 otmp->oartifact ? artifact_name(xname(otmp), &dummy)
985 : OBJ_NAME(objects[otmp->otyp]),
986 value, currency(value), points);
987 putstr(endwin, 0, pbuf);
990 if (Has_contents(otmp))
991 artifact_score(otmp->cobj, counting, endwin);
995 /* Be careful not to call panic from here! */
996 void
997 done(how)
998 int how;
1000 if (how == TRICKED) {
1001 if (killer.name[0]) {
1002 paniclog("trickery", killer.name);
1003 killer.name[0] = 0;
1005 if (wizard) {
1006 You("are a very tricky wizard, it seems.");
1007 return;
1010 if (program_state.panicking
1011 #ifdef HANGUPHANDLING
1012 || program_state.done_hup
1013 #endif
1015 /* skip status update if panicking or disconnected */
1016 context.botl = context.botlx = FALSE;
1017 } else {
1018 /* otherwise force full status update */
1019 context.botlx = TRUE;
1020 bot();
1023 if (how == ASCENDED || (!killer.name[0] && how == GENOCIDED))
1024 killer.format = NO_KILLER_PREFIX;
1025 /* Avoid killed by "a" burning or "a" starvation */
1026 if (!killer.name[0] && (how == STARVING || how == BURNING))
1027 killer.format = KILLED_BY;
1028 if (!killer.name[0] || how >= PANICKED)
1029 Strcpy(killer.name, deaths[how]);
1031 if (how < PANICKED)
1032 u.umortality++;
1033 if (Lifesaved && (how <= GENOCIDED)) {
1034 pline("But wait...");
1035 makeknown(AMULET_OF_LIFE_SAVING);
1036 Your("medallion %s!", !Blind ? "begins to glow" : "feels warm");
1037 if (how == CHOKING)
1038 You("vomit ...");
1039 You_feel("much better!");
1040 pline_The("medallion crumbles to dust!");
1041 if (uamul)
1042 useup(uamul);
1044 (void) adjattrib(A_CON, -1, TRUE);
1045 savelife(how);
1046 if (how == GENOCIDED) {
1047 pline("Unfortunately you are still genocided...");
1048 } else {
1049 killer.name[0] = 0;
1050 killer.format = 0;
1051 return;
1054 if ((wizard || discover) && (how <= GENOCIDED)
1055 && !paranoid_query(ParanoidDie, "Die?")) {
1056 pline("OK, so you don't %s.", (how == CHOKING) ? "choke" : "die");
1057 savelife(how);
1058 killer.name[0] = 0;
1059 killer.format = 0;
1060 return;
1062 really_done(how);
1065 /* separated from done() in order to specify the __noreturn__ attribute */
1066 STATIC_OVL void
1067 really_done(how)
1068 int how;
1070 boolean taken;
1071 char pbuf[BUFSZ];
1072 winid endwin = WIN_ERR;
1073 boolean bones_ok, have_windows = iflags.window_inited;
1074 struct obj *corpse = (struct obj *) 0;
1075 time_t endtime;
1076 long umoney;
1077 long tmp;
1080 * The game is now over...
1082 program_state.gameover = 1;
1083 /* in case of a subsequent panic(), there's no point trying to save */
1084 program_state.something_worth_saving = 0;
1085 /* render vision subsystem inoperative */
1086 iflags.vision_inited = 0;
1088 /* might have been killed while using a disposable item, so make sure
1089 it's gone prior to inventory disclosure and creation of bones data */
1090 inven_inuse(TRUE);
1091 /* maybe not on object lists; if an active light source, would cause
1092 big trouble (`obj_is_local' panic) for savebones() -> savelev() */
1093 if (thrownobj && thrownobj->where == OBJ_FREE)
1094 dealloc_obj(thrownobj);
1095 if (kickedobj && kickedobj->where == OBJ_FREE)
1096 dealloc_obj(kickedobj);
1098 /* remember time of death here instead of having bones, rip, and
1099 topten figure it out separately and possibly getting different
1100 time or even day if player is slow responding to --More-- */
1101 urealtime.finish_time = endtime = getnow();
1102 urealtime.realtime += (long) (endtime - urealtime.start_timing);
1104 dump_open_log(endtime);
1105 /* Sometimes you die on the first move. Life's not fair.
1106 * On those rare occasions you get hosed immediately, go out
1107 * smiling... :-) -3.
1109 if (moves <= 1 && how < PANICKED) /* You die... --More-- */
1110 pline("Do not pass go. Do not collect 200 %s.", currency(200L));
1112 if (have_windows)
1113 wait_synch(); /* flush screen output */
1114 #ifndef NO_SIGNAL
1115 (void) signal(SIGINT, (SIG_RET_TYPE) done_intr);
1116 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
1117 (void) signal(SIGQUIT, (SIG_RET_TYPE) done_intr);
1118 sethanguphandler(done_hangup);
1119 #endif
1120 #endif /* NO_SIGNAL */
1122 bones_ok = (how < GENOCIDED) && can_make_bones();
1124 if (bones_ok && launch_in_progress())
1125 force_launch_placement();
1127 /* maintain ugrave_arise even for !bones_ok */
1128 if (how == PANICKED)
1129 u.ugrave_arise = (NON_PM - 3); /* no corpse, no grave */
1130 else if (how == BURNING || how == DISSOLVED) /* corpse burns up too */
1131 u.ugrave_arise = (NON_PM - 2); /* leave no corpse */
1132 else if (how == STONING)
1133 u.ugrave_arise = (NON_PM - 1); /* statue instead of corpse */
1134 else if (how == TURNED_SLIME)
1135 u.ugrave_arise = PM_GREEN_SLIME;
1137 /* if pets will contribute to score, populate mydogs list now
1138 (bones creation isn't a factor, but pline() messaging is) */
1139 if (how == ESCAPED || how == ASCENDED)
1140 keepdogs(TRUE);
1142 if (how == QUIT) {
1143 killer.format = NO_KILLER_PREFIX;
1144 if (u.uhp < 1) {
1145 how = DIED;
1146 u.umortality++; /* skipped above when how==QUIT */
1147 Strcpy(killer.name, "quit while already on Charon's boat");
1150 if (how == ESCAPED || how == PANICKED)
1151 killer.format = NO_KILLER_PREFIX;
1153 fixup_death(how); /* actually, fixup multi_reason */
1155 if (how != PANICKED) {
1156 /* these affect score and/or bones, but avoid them during panic */
1157 taken = paybill((how == ESCAPED) ? -1 : (how != QUIT));
1158 paygd();
1159 clearpriests();
1160 } else
1161 taken = FALSE; /* lint; assert( !bones_ok ); */
1163 clearlocks();
1165 if (have_windows)
1166 display_nhwindow(WIN_MESSAGE, FALSE);
1168 if (strcmp(flags.end_disclose, "none") && how != PANICKED)
1169 disclose(how, taken);
1171 dump_everything(how);
1173 /* finish_paybill should be called after disclosure but before bones */
1174 if (bones_ok && taken)
1175 finish_paybill();
1177 /* grave creation should be after disclosure so it doesn't have
1178 this grave in the current level's features for #overview */
1179 if (bones_ok && u.ugrave_arise == NON_PM
1180 && !(mvitals[u.umonnum].mvflags & G_NOCORPSE)) {
1181 int mnum = u.umonnum;
1183 if (!Upolyd) {
1184 /* Base corpse on race when not poly'd since original
1185 * u.umonnum is based on role, and all role monsters
1186 * are human.
1188 mnum = (flags.female && urace.femalenum != NON_PM)
1189 ? urace.femalenum
1190 : urace.malenum;
1192 corpse = mk_named_object(CORPSE, &mons[mnum], u.ux, u.uy, plname);
1193 Sprintf(pbuf, "%s, ", plname);
1194 formatkiller(eos(pbuf), sizeof pbuf - strlen(pbuf), how, TRUE);
1195 make_grave(u.ux, u.uy, pbuf);
1197 pbuf[0] = '\0'; /* clear grave text; also lint suppression */
1199 /* calculate score, before creating bones [container gold] */
1201 int deepest = deepest_lev_reached(FALSE);
1203 umoney = money_cnt(invent);
1204 tmp = u.umoney0;
1205 umoney += hidden_gold(); /* accumulate gold from containers */
1206 tmp = umoney - tmp; /* net gain */
1208 if (tmp < 0L)
1209 tmp = 0L;
1210 if (how < PANICKED)
1211 tmp -= tmp / 10L;
1212 tmp += 50L * (long) (deepest - 1);
1213 if (deepest > 20)
1214 tmp += 1000L * (long) ((deepest > 30) ? 10 : deepest - 20);
1215 nowrap_add(u.urexp, tmp);
1217 /* ascension gives a score bonus iff offering to original deity */
1218 if (how == ASCENDED && u.ualign.type == u.ualignbase[A_ORIGINAL]) {
1219 /* retaining original alignment: score *= 2;
1220 converting, then using helm-of-OA to switch back: *= 1.5 */
1221 tmp = (u.ualignbase[A_CURRENT] == u.ualignbase[A_ORIGINAL])
1222 ? u.urexp
1223 : (u.urexp / 2L);
1224 nowrap_add(u.urexp, tmp);
1228 if (u.ugrave_arise >= LOW_PM && u.ugrave_arise != PM_GREEN_SLIME) {
1229 /* give this feedback even if bones aren't going to be created,
1230 so that its presence or absence doesn't tip off the player to
1231 new bones or their lack; it might be a lie if makemon fails */
1232 Your("body rises from the dead as %s...",
1233 an(mons[u.ugrave_arise].mname));
1234 display_nhwindow(WIN_MESSAGE, FALSE);
1237 if (bones_ok) {
1238 if (!wizard || paranoid_query(ParanoidBones, "Save bones?"))
1239 savebones(how, endtime, corpse);
1240 /* corpse may be invalid pointer now so
1241 ensure that it isn't used again */
1242 corpse = (struct obj *) 0;
1245 /* update gold for the rip output, which can't use hidden_gold()
1246 (containers will be gone by then if bones just got saved...) */
1247 done_money = umoney;
1249 /* clean up unneeded windows */
1250 if (have_windows) {
1251 wait_synch();
1252 free_pickinv_cache(); /* extra persistent window if perm_invent */
1253 if (WIN_INVEN != WIN_ERR)
1254 destroy_nhwindow(WIN_INVEN), WIN_INVEN = WIN_ERR;
1255 display_nhwindow(WIN_MESSAGE, TRUE);
1256 destroy_nhwindow(WIN_MAP), WIN_MAP = WIN_ERR;
1257 #ifndef STATUS_VIA_WINDOWPORT
1258 destroy_nhwindow(WIN_STATUS), WIN_STATUS = WIN_ERR;
1259 #endif
1260 destroy_nhwindow(WIN_MESSAGE), WIN_MESSAGE = WIN_ERR;
1262 if (!done_stopprint || flags.tombstone)
1263 endwin = create_nhwindow(NHW_TEXT);
1265 if (how < GENOCIDED && flags.tombstone && endwin != WIN_ERR)
1266 outrip(endwin, how, endtime);
1267 } else
1268 done_stopprint = 1; /* just avoid any more output */
1270 #ifdef DUMPLOG
1271 dump_redirect(TRUE);
1272 genl_outrip(0, how, endtime);
1273 dump_redirect(FALSE);
1274 #endif
1275 if (u.uhave.amulet) {
1276 Strcat(killer.name, " (with the Amulet)");
1277 } else if (how == ESCAPED) {
1278 if (Is_astralevel(&u.uz)) /* offered Amulet to wrong deity */
1279 Strcat(killer.name, " (in celestial disgrace)");
1280 else if (carrying(FAKE_AMULET_OF_YENDOR))
1281 Strcat(killer.name, " (with a fake Amulet)");
1282 /* don't bother counting to see whether it should be plural */
1285 Sprintf(pbuf, "%s %s the %s...", Goodbye(), plname,
1286 (how != ASCENDED)
1287 ? (const char *) ((flags.female && urole.name.f)
1288 ? urole.name.f
1289 : urole.name.m)
1290 : (const char *) (flags.female ? "Demigoddess" : "Demigod"));
1291 dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1292 dump_forward_putstr(endwin, 0, "", done_stopprint);
1294 if (how == ESCAPED || how == ASCENDED) {
1295 struct monst *mtmp;
1296 struct obj *otmp;
1297 register struct val_list *val;
1298 register int i;
1300 for (val = valuables; val->list; val++)
1301 for (i = 0; i < val->size; i++) {
1302 val->list[i].count = 0L;
1304 get_valuables(invent);
1306 /* add points for collected valuables */
1307 for (val = valuables; val->list; val++)
1308 for (i = 0; i < val->size; i++)
1309 if (val->list[i].count != 0L) {
1310 tmp = val->list[i].count
1311 * (long) objects[val->list[i].typ].oc_cost;
1312 nowrap_add(u.urexp, tmp);
1315 /* count the points for artifacts */
1316 artifact_score(invent, TRUE, endwin);
1317 #ifdef DUMPLOG
1318 dump_redirect(TRUE);
1319 artifact_score(invent, TRUE, endwin);
1320 dump_redirect(FALSE);
1321 #endif
1323 viz_array[0][0] |= IN_SIGHT; /* need visibility for naming */
1324 mtmp = mydogs;
1325 Strcpy(pbuf, "You");
1326 if (!Schroedingers_cat) /* check here in case disclosure was off */
1327 Schroedingers_cat = odds_and_ends(invent, CAT_CHECK);
1328 if (Schroedingers_cat) {
1329 int mhp, m_lev = adj_lev(&mons[PM_HOUSECAT]);
1330 mhp = d(m_lev, 8);
1331 nowrap_add(u.urexp, mhp);
1332 Strcat(eos(pbuf), " and Schroedinger's cat");
1334 if (mtmp) {
1335 while (mtmp) {
1336 Sprintf(eos(pbuf), " and %s", mon_nam(mtmp));
1337 if (mtmp->mtame)
1338 nowrap_add(u.urexp, mtmp->mhp);
1339 mtmp = mtmp->nmon;
1341 dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1342 pbuf[0] = '\0';
1343 } else {
1344 Strcat(pbuf, " ");
1346 Sprintf(eos(pbuf), "%s with %ld point%s,",
1347 how == ASCENDED ? "went to your reward"
1348 : "escaped from the dungeon",
1349 u.urexp, plur(u.urexp));
1350 dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1352 if (!done_stopprint)
1353 artifact_score(invent, FALSE, endwin); /* list artifacts */
1354 #if DUMPLOG
1355 dump_redirect(TRUE);
1356 artifact_score(invent, FALSE, 0);
1357 dump_redirect(FALSE);
1358 #endif
1360 /* list valuables here */
1361 for (val = valuables; val->list; val++) {
1362 sort_valuables(val->list, val->size);
1363 for (i = 0; i < val->size && !done_stopprint; i++) {
1364 int typ = val->list[i].typ;
1365 long count = val->list[i].count;
1367 if (count == 0L)
1368 continue;
1369 if (objects[typ].oc_class != GEM_CLASS || typ <= LAST_GEM) {
1370 otmp = mksobj(typ, FALSE, FALSE);
1371 makeknown(otmp->otyp);
1372 otmp->known = 1; /* for fake amulets */
1373 otmp->dknown = 1; /* seen it (blindness fix) */
1374 if (has_oname(otmp))
1375 free_oname(otmp);
1376 otmp->quan = count;
1377 Sprintf(pbuf, "%8ld %s (worth %ld %s),", count,
1378 xname(otmp), count * (long) objects[typ].oc_cost,
1379 currency(2L));
1380 obfree(otmp, (struct obj *) 0);
1381 } else {
1382 Sprintf(pbuf, "%8ld worthless piece%s of colored glass,",
1383 count, plur(count));
1385 dump_forward_putstr(endwin, 0, pbuf, 0);
1389 } else {
1390 /* did not escape or ascend */
1391 if (u.uz.dnum == 0 && u.uz.dlevel <= 0) {
1392 /* level teleported out of the dungeon; `how' is DIED,
1393 due to falling or to "arriving at heaven prematurely" */
1394 Sprintf(pbuf, "You %s beyond the confines of the dungeon",
1395 (u.uz.dlevel < 0) ? "passed away" : ends[how]);
1396 } else {
1397 /* more conventional demise */
1398 const char *where = dungeons[u.uz.dnum].dname;
1400 if (Is_astralevel(&u.uz))
1401 where = "The Astral Plane";
1402 Sprintf(pbuf, "You %s in %s", ends[how], where);
1403 if (!In_endgame(&u.uz) && !Is_knox(&u.uz))
1404 Sprintf(eos(pbuf), " on dungeon level %d",
1405 In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz));
1408 Sprintf(eos(pbuf), " with %ld point%s,", u.urexp, plur(u.urexp));
1409 dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1412 Sprintf(pbuf, "and %ld piece%s of gold, after %ld move%s.", umoney,
1413 plur(umoney), moves, plur(moves));
1414 dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1415 Sprintf(pbuf,
1416 "You were level %d with a maximum of %d hit point%s when you %s.",
1417 u.ulevel, u.uhpmax, plur(u.uhpmax), ends[how]);
1418 dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1419 dump_forward_putstr(endwin, 0, "", done_stopprint);
1420 if (!done_stopprint)
1421 display_nhwindow(endwin, TRUE);
1422 if (endwin != WIN_ERR)
1423 destroy_nhwindow(endwin);
1425 dump_close_log();
1426 /* "So when I die, the first thing I will see in Heaven is a
1427 * score list?" */
1428 if (have_windows && !iflags.toptenwin)
1429 exit_nhwindows((char *) 0), have_windows = FALSE;
1430 topten(how, endtime);
1431 if (have_windows)
1432 exit_nhwindows((char *) 0);
1434 if (done_stopprint) {
1435 raw_print("");
1436 raw_print("");
1438 terminate(EXIT_SUCCESS);
1441 void
1442 container_contents(list, identified, all_containers, reportempty)
1443 struct obj *list;
1444 boolean identified, all_containers, reportempty;
1446 register struct obj *box, *obj;
1447 char buf[BUFSZ];
1448 boolean cat, deadcat;
1450 for (box = list; box; box = box->nobj) {
1451 if (Is_container(box) || box->otyp == STATUE) {
1452 box->cknown = 1; /* we're looking at the contents now */
1453 if (identified)
1454 box->lknown = 1;
1455 cat = deadcat = FALSE;
1456 if (SchroedingersBox(box) && !Schroedingers_cat) {
1457 /* Schroedinger's Cat? */
1458 cat = odds_and_ends(box, CAT_CHECK);
1459 if (cat)
1460 Schroedingers_cat = TRUE;
1461 else
1462 deadcat = TRUE;
1463 box->spe = 0;
1465 if (box->otyp == BAG_OF_TRICKS) {
1466 continue; /* wrong type of container */
1467 } else if (box->cobj) {
1468 winid tmpwin = create_nhwindow(NHW_MENU);
1470 sortloot(&box->cobj,
1471 (((flags.sortloot == 'l' || flags.sortloot == 'f')
1472 ? SORTLOOT_LOOT : 0)
1473 | (flags.sortpack ? SORTLOOT_PACK : 0)),
1474 FALSE);
1475 Sprintf(buf, "Contents of %s:", the(xname(box)));
1476 putstr(tmpwin, 0, buf);
1477 putstr(tmpwin, 0, "");
1478 for (obj = box->cobj; obj; obj = obj->nobj) {
1479 if (identified) {
1480 makeknown(obj->otyp);
1481 obj->known = obj->bknown = obj->dknown
1482 = obj->rknown = 1;
1483 if (Is_container(obj) || obj->otyp == STATUE)
1484 obj->cknown = obj->lknown = 1;
1486 putstr(tmpwin, 0, doname(obj));
1488 if (cat)
1489 putstr(tmpwin, 0, "Schroedinger's cat");
1490 else if (deadcat)
1491 putstr(tmpwin, 0, "Schroedinger's dead cat");
1492 display_nhwindow(tmpwin, TRUE);
1493 destroy_nhwindow(tmpwin);
1494 if (all_containers)
1495 container_contents(box->cobj, identified, TRUE,
1496 reportempty);
1497 } else if (cat || deadcat) {
1498 pline("%s Schroedinger's %scat!", Tobjnam(box, "contain"),
1499 deadcat ? "dead " : "");
1500 display_nhwindow(WIN_MESSAGE, FALSE);
1501 } else if (reportempty) {
1502 pline("%s is empty.", upstart(thesimpleoname(box)));
1503 display_nhwindow(WIN_MESSAGE, FALSE);
1506 if (!all_containers)
1507 break;
1511 /* should be called with either EXIT_SUCCESS or EXIT_FAILURE */
1512 void
1513 terminate(status)
1514 int status;
1516 program_state.in_moveloop = 0; /* won't be returning to normal play */
1517 #ifdef MAC
1518 getreturn("to exit");
1519 #endif
1520 /* don't bother to try to release memory if we're in panic mode, to
1521 avoid trouble in case that happens to be due to memory problems */
1522 if (!program_state.panicking) {
1523 freedynamicdata();
1524 dlb_cleanup();
1527 #ifdef VMS
1529 * This is liable to draw a warning if compiled with gcc, but it's
1530 * more important to flag panic() -> really_done() -> terminate()
1531 * as __noreturn__ then to avoid the warning.
1533 /* don't call exit() if already executing within an exit handler;
1534 that would cancel any other pending user-mode handlers */
1535 if (program_state.exiting)
1536 return;
1537 #endif
1538 program_state.exiting = 1;
1539 nethack_exit(status);
1542 extern const int monstr[];
1544 static const char *vanqorders[] = {
1545 "traditional: by monster level, by internal monster index",
1546 #define VANQ_MLVL_MNDX 0
1547 "by monster toughness, by internal monster index",
1548 #define VANQ_MSTR_MNDX 1
1549 "alphabetically, first unique monsters, then others",
1550 #define VANQ_ALPHA_SEP 2
1551 "alphabetically, unique monsters and others intermixed",
1552 #define VANQ_ALPHA_MIX 3
1553 "by monster class, high to low level within class",
1554 #define VANQ_MCLS_HTOL 4
1555 "by monster class, low to high level within class",
1556 #define VANQ_MCLS_LTOH 5
1557 "by count, high to low, by internal index within tied count",
1558 #define VANQ_COUNT_H_L 6
1559 "by count, low to high, by internal index within tied count",
1560 #define VANQ_COUNT_L_H 7
1562 static int vanq_sortmode = VANQ_MLVL_MNDX;
1564 STATIC_PTR int CFDECLSPEC
1565 vanqsort_cmp(vptr1, vptr2)
1566 const genericptr vptr1;
1567 const genericptr vptr2;
1569 int indx1 = *(short *) vptr1, indx2 = *(short *) vptr2,
1570 mlev1, mlev2, mstr1, mstr2, uniq1, uniq2, died1, died2, res;
1571 const char *name1, *name2, *punct;
1572 schar mcls1, mcls2;
1574 switch (vanq_sortmode) {
1575 default:
1576 case VANQ_MLVL_MNDX:
1577 /* sort by monster level */
1578 mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
1579 res = mlev2 - mlev1; /* mlevel high to low */
1580 break;
1581 case VANQ_MSTR_MNDX:
1582 /* sort by monster toughness */
1583 mstr1 = monstr[indx1], mstr2 = monstr[indx2];
1584 res = mstr2 - mstr1; /* monstr high to low */
1585 break;
1586 case VANQ_ALPHA_SEP:
1587 uniq1 = ((mons[indx1].geno & G_UNIQ) && indx1 != PM_HIGH_PRIEST);
1588 uniq2 = ((mons[indx2].geno & G_UNIQ) && indx2 != PM_HIGH_PRIEST);
1589 if (uniq1 ^ uniq2) { /* one or other uniq, but not both */
1590 res = uniq2 - uniq1;
1591 break;
1592 } /* else both unique or neither unique */
1593 /*FALLTHRU*/
1594 case VANQ_ALPHA_MIX:
1595 name1 = mons[indx1].mname, name2 = mons[indx2].mname;
1596 res = strcmpi(name1, name2); /* caseblind alhpa, low to high */
1597 break;
1598 case VANQ_MCLS_HTOL:
1599 case VANQ_MCLS_LTOH:
1600 /* mons[].mlet is a small integer, 1..N, of type plain char;
1601 if 'char' happens to be unsigned, (mlet1 - mlet2) would yield
1602 an inappropriate result when mlet2 is greater than mlet1,
1603 so force our copies (mcls1, mcls2) to be signed */
1604 mcls1 = (schar) mons[indx1].mlet, mcls2 = (schar) mons[indx2].mlet;
1605 /* S_ANT through S_ZRUTY correspond to lowercase monster classes,
1606 S_ANGEL through S_ZOMBIE correspond to uppercase, and various
1607 punctuation characters are used for classes beyond those */
1608 if (mcls1 > S_ZOMBIE && mcls2 > S_ZOMBIE) {
1609 /* force a specific order to the punctuation classes that's
1610 different from the internal order;
1611 internal order is ok if neither or just one is punctuation
1612 since letters have lower values so come out before punct */
1613 static const char punctclasses[] = {
1614 S_LIZARD, S_EEL, S_GOLEM, S_GHOST, S_DEMON, S_HUMAN, '\0'
1617 if ((punct = index(punctclasses, mcls1)) != 0)
1618 mcls1 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
1619 if ((punct = index(punctclasses, mcls2)) != 0)
1620 mcls2 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
1622 res = mcls1 - mcls2; /* class */
1623 if (res == 0) {
1624 mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
1625 res = mlev1 - mlev2; /* mlevel low to high */
1626 if (vanq_sortmode == VANQ_MCLS_HTOL)
1627 res = -res; /* mlevel high to low */
1629 break;
1630 case VANQ_COUNT_H_L:
1631 case VANQ_COUNT_L_H:
1632 died1 = mvitals[indx1].died, died2 = mvitals[indx2].died;
1633 res = died2 - died1; /* dead count high to low */
1634 if (vanq_sortmode == VANQ_COUNT_L_H)
1635 res = -res; /* dead count low to high */
1636 break;
1638 /* tiebreaker: internal mons[] index */
1639 if (res == 0)
1640 res = indx1 - indx2; /* mndx low to high */
1641 return res;
1644 /* returns -1 if cancelled via ESC */
1645 STATIC_OVL int
1646 set_vanq_order()
1648 winid tmpwin;
1649 menu_item *selected;
1650 anything any;
1651 int i, n, choice;
1653 tmpwin = create_nhwindow(NHW_MENU);
1654 start_menu(tmpwin);
1655 any = zeroany; /* zero out all bits */
1656 for (i = 0; i < SIZE(vanqorders); i++) {
1657 if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */
1658 continue;
1659 any.a_int = i + 1;
1660 add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, vanqorders[i],
1661 (i == vanq_sortmode) ? MENU_SELECTED : MENU_UNSELECTED);
1663 end_menu(tmpwin, "Sort order for vanquished monster counts");
1665 n = select_menu(tmpwin, PICK_ONE, &selected);
1666 destroy_nhwindow(tmpwin);
1667 if (n > 0) {
1668 choice = selected[0].item.a_int - 1;
1669 /* skip preselected entry if we have more than one item chosen */
1670 if (n > 1 && choice == vanq_sortmode)
1671 choice = selected[1].item.a_int - 1;
1672 free((genericptr_t) selected);
1673 vanq_sortmode = choice;
1675 return (n < 0) ? -1 : vanq_sortmode;
1678 /* #vanquished command */
1680 dovanquished()
1682 list_vanquished('a', FALSE);
1683 return 0;
1686 /* high priests aren't unique but are flagged as such to simplify something */
1687 #define UniqCritterIndx(mndx) ((mons[mndx].geno & G_UNIQ) \
1688 && mndx != PM_HIGH_PRIEST)
1690 STATIC_OVL void
1691 list_vanquished(defquery, ask)
1692 char defquery;
1693 boolean ask;
1695 register int i;
1696 int pfx, nkilled;
1697 unsigned ntypes, ni;
1698 long total_killed = 0L;
1699 winid klwin;
1700 short mindx[NUMMONS];
1701 char c, buf[BUFSZ], buftoo[BUFSZ];
1703 /* get totals first */
1704 ntypes = 0;
1705 for (i = LOW_PM; i < NUMMONS; i++) {
1706 if ((nkilled = (int) mvitals[i].died) == 0)
1707 continue;
1708 mindx[ntypes++] = i;
1709 total_killed += (long) nkilled;
1712 /* vanquished creatures list;
1713 * includes all dead monsters, not just those killed by the player
1715 if (ntypes != 0) {
1716 char mlet, prev_mlet = 0; /* used as small integer, not character */
1717 boolean class_header, uniq_header, was_uniq = FALSE;
1719 c = ask ? yn_function(
1720 "Do you want an account of creatures vanquished?",
1721 ynaqchars, defquery)
1722 : defquery;
1723 if (c == 'q')
1724 done_stopprint++;
1725 if (c == 'y' || c == 'a') {
1726 if (c == 'a') { /* ask player to choose sort order */
1727 /* choose value for vanq_sortmode via menu; ESC cancels list
1728 of vanquished monsters but does not set 'done_stopprint' */
1729 if (set_vanq_order() < 0)
1730 return;
1732 uniq_header = (vanq_sortmode == VANQ_ALPHA_SEP);
1733 class_header = (vanq_sortmode == VANQ_MCLS_LTOH
1734 || vanq_sortmode == VANQ_MCLS_HTOL);
1736 klwin = create_nhwindow(NHW_MENU);
1737 putstr(klwin, 0, "Vanquished creatures:");
1738 putstr(klwin, 0, "");
1740 qsort((genericptr_t) mindx, ntypes, sizeof *mindx, vanqsort_cmp);
1741 for (ni = 0; ni < ntypes; ni++) {
1742 i = mindx[ni];
1743 nkilled = mvitals[i].died;
1744 mlet = mons[i].mlet;
1745 if (class_header && mlet != prev_mlet) {
1746 Strcpy(buf, def_monsyms[(int) mlet].explain);
1747 putstr(klwin, ask ? 0 : iflags.menu_headings,
1748 upstart(buf));
1749 prev_mlet = mlet;
1751 if (UniqCritterIndx(i)) {
1752 Sprintf(buf, "%s%s",
1753 !type_is_pname(&mons[i]) ? "the " : "",
1754 mons[i].mname);
1755 if (nkilled > 1) {
1756 switch (nkilled) {
1757 case 2:
1758 Sprintf(eos(buf), " (twice)");
1759 break;
1760 case 3:
1761 Sprintf(eos(buf), " (thrice)");
1762 break;
1763 default:
1764 Sprintf(eos(buf), " (%d times)", nkilled);
1765 break;
1768 was_uniq = TRUE;
1769 } else {
1770 if (uniq_header && was_uniq) {
1771 putstr(klwin, 0, "");
1772 was_uniq = FALSE;
1774 /* trolls or undead might have come back,
1775 but we don't keep track of that */
1776 if (nkilled == 1)
1777 Strcpy(buf, an(mons[i].mname));
1778 else
1779 Sprintf(buf, "%3d %s", nkilled,
1780 makeplural(mons[i].mname));
1782 /* number of leading spaces to match 3 digit prefix */
1783 pfx = !strncmpi(buf, "the ", 3) ? 0
1784 : !strncmpi(buf, "an ", 3) ? 1
1785 : !strncmpi(buf, "a ", 2) ? 2
1786 : !digit(buf[2]) ? 4 : 0;
1787 if (class_header)
1788 ++pfx;
1789 Sprintf(buftoo, "%*s%s", pfx, "", buf);
1790 putstr(klwin, 0, buftoo);
1793 * if (Hallucination)
1794 * putstr(klwin, 0, "and a partridge in a pear tree");
1796 if (ntypes > 1) {
1797 putstr(klwin, 0, "");
1798 Sprintf(buf, "%ld creatures vanquished.", total_killed);
1799 putstr(klwin, 0, buf);
1801 display_nhwindow(klwin, TRUE);
1802 destroy_nhwindow(klwin);
1804 } else if (defquery == 'a') {
1805 /* #dovanquished rather than final disclosure, so pline() is ok */
1806 pline("No monsters have been vanquished.");
1810 /* number of monster species which have been genocided */
1812 num_genocides()
1814 int i, n = 0;
1816 for (i = LOW_PM; i < NUMMONS; ++i) {
1817 if (mvitals[i].mvflags & G_GENOD) {
1818 ++n;
1819 if (UniqCritterIndx(i))
1820 impossible("unique creature '%d: %s' genocided?",
1821 i, mons[i].mname);
1824 return n;
1827 STATIC_OVL int
1828 num_extinct()
1830 int i, n = 0;
1832 for (i = LOW_PM; i < NUMMONS; ++i) {
1833 if (UniqCritterIndx(i))
1834 continue;
1835 if ((mvitals[i].mvflags & G_GONE) == G_EXTINCT)
1836 ++n;
1838 return n;
1841 STATIC_OVL void
1842 list_genocided(defquery, ask)
1843 char defquery;
1844 boolean ask;
1846 register int i;
1847 int ngenocided, nextinct;
1848 char c;
1849 winid klwin;
1850 char buf[BUFSZ];
1852 ngenocided = num_genocides();
1853 nextinct = num_extinct();
1855 /* genocided or extinct species list */
1856 if (ngenocided != 0 || nextinct != 0) {
1857 Sprintf(buf, "Do you want a list of %sspecies%s%s?",
1858 (nextinct && !ngenocided) ? "extinct " : "",
1859 (ngenocided) ? " genocided" : "",
1860 (nextinct && ngenocided) ? " and extinct" : "");
1861 c = ask ? yn_function(buf, ynqchars, defquery) : defquery;
1862 if (c == 'q')
1863 done_stopprint++;
1864 if (c == 'y') {
1865 klwin = create_nhwindow(NHW_MENU);
1866 Sprintf(buf, "%s%s species:",
1867 (ngenocided) ? "Genocided" : "Extinct",
1868 (nextinct && ngenocided) ? " or extinct" : "");
1869 putstr(klwin, 0, buf);
1870 putstr(klwin, 0, "");
1872 for (i = LOW_PM; i < NUMMONS; i++) {
1873 /* uniques can't be genocided but can become extinct;
1874 however, they're never reported as extinct, so skip them */
1875 if (UniqCritterIndx(i))
1876 continue;
1877 if (mvitals[i].mvflags & G_GONE) {
1878 Strcpy(buf, makeplural(mons[i].mname));
1880 * "Extinct" is unfortunate terminology. A species
1881 * is marked extinct when its birth limit is reached,
1882 * but there might be members of the species still
1883 * alive, contradicting the meaning of the word.
1885 if ((mvitals[i].mvflags & G_GONE) == G_EXTINCT)
1886 Strcat(buf, " (extinct)");
1887 putstr(klwin, 0, buf);
1890 putstr(klwin, 0, "");
1891 if (ngenocided > 0) {
1892 Sprintf(buf, "%d species genocided.", ngenocided);
1893 putstr(klwin, 0, buf);
1895 if (nextinct > 0) {
1896 Sprintf(buf, "%d species extinct.", nextinct);
1897 putstr(klwin, 0, buf);
1900 display_nhwindow(klwin, TRUE);
1901 destroy_nhwindow(klwin);
1906 /* set a delayed killer, ensure non-delayed killer is cleared out */
1907 void
1908 delayed_killer(id, format, killername)
1909 int id;
1910 int format;
1911 const char *killername;
1913 struct kinfo *k = find_delayed_killer(id);
1915 if (k == (struct kinfo *) 0) {
1916 /* no match, add a new delayed killer to the list */
1917 k = (struct kinfo *) alloc(sizeof(struct kinfo));
1918 (void) memset((genericptr_t)k, 0, sizeof(struct kinfo));
1919 k->id = id;
1920 k->next = killer.next;
1921 killer.next = k;
1924 k->format = format;
1925 Strcpy(k->name, killername ? killername : "");
1926 killer.name[0] = 0;
1929 struct kinfo *
1930 find_delayed_killer(id)
1931 int id;
1933 struct kinfo *k;
1935 for (k = killer.next; k != (struct kinfo *) 0; k = k->next) {
1936 if (k->id == id)
1937 break;
1939 return k;
1942 void
1943 dealloc_killer(kptr)
1944 struct kinfo *kptr;
1946 struct kinfo *prev = &killer, *k;
1948 if (kptr == (struct kinfo *) 0)
1949 return;
1950 for (k = killer.next; k != (struct kinfo *) 0; k = k->next) {
1951 if (k == kptr)
1952 break;
1953 prev = k;
1956 if (k == (struct kinfo *) 0) {
1957 impossible("dealloc_killer not on list");
1958 } else {
1959 prev->next = k->next;
1960 free((genericptr_t) k);
1964 void
1965 save_killers(fd, mode)
1966 int fd;
1967 int mode;
1969 struct kinfo *kptr;
1971 if (perform_bwrite(mode)) {
1972 for (kptr = &killer; kptr != (struct kinfo *) 0; kptr = kptr->next) {
1973 bwrite(fd, (genericptr_t) kptr, sizeof(struct kinfo));
1976 if (release_data(mode)) {
1977 while (killer.next) {
1978 kptr = killer.next->next;
1979 free((genericptr_t) killer.next);
1980 killer.next = kptr;
1985 void
1986 restore_killers(fd)
1987 int fd;
1989 struct kinfo *kptr;
1991 for (kptr = &killer; kptr != (struct kinfo *) 0; kptr = kptr->next) {
1992 mread(fd, (genericptr_t) kptr, sizeof(struct kinfo));
1993 if (kptr->next) {
1994 kptr->next = (struct kinfo *) alloc(sizeof(struct kinfo));
1999 static int
2000 wordcount(p)
2001 char *p;
2003 int words = 0;
2005 while (*p) {
2006 while (*p && isspace((uchar) *p))
2007 p++;
2008 if (*p)
2009 words++;
2010 while (*p && !isspace((uchar) *p))
2011 p++;
2013 return words;
2016 static void
2017 bel_copy1(inp, out)
2018 char **inp, *out;
2020 char *in = *inp;
2022 out += strlen(out); /* eos() */
2023 while (*in && isspace((uchar) *in))
2024 in++;
2025 while (*in && !isspace((uchar) *in))
2026 *out++ = *in++;
2027 *out = '\0';
2028 *inp = in;
2031 char *
2032 build_english_list(in)
2033 char *in;
2035 char *out, *p = in;
2036 int len = (int) strlen(p), words = wordcount(p);
2038 /* +3: " or " - " "; +(words - 1): (N-1)*(", " - " ") */
2039 if (words > 1)
2040 len += 3 + (words - 1);
2041 out = (char *) alloc(len + 1);
2042 *out = '\0'; /* bel_copy1() appends */
2044 switch (words) {
2045 case 0:
2046 impossible("no words in list");
2047 break;
2048 case 1:
2049 /* "single" */
2050 bel_copy1(&p, out);
2051 break;
2052 default:
2053 if (words == 2) {
2054 /* "first or second" */
2055 bel_copy1(&p, out);
2056 Strcat(out, " ");
2057 } else {
2058 /* "first, second, or third */
2059 do {
2060 bel_copy1(&p, out);
2061 Strcat(out, ", ");
2062 } while (--words > 1);
2064 Strcat(out, "or ");
2065 bel_copy1(&p, out);
2066 break;
2068 return out;
2071 /*end.c*/