1 /* NetHack 3.6	end.c	$NHDT-Date: 1575245059 2019/12/02 00:04:19 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.181 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2012. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #define NEED_VARARGS /* comment line for pre-compiled headers */
7 
8 #include "hack.h"
9 #include "lev.h"
10 #ifndef NO_SIGNAL
11 #include <signal.h>
12 #endif
13 #include <ctype.h>
14 #ifndef LONG_MAX
15 #include <limits.h>
16 #endif
17 #include "dlb.h"
18 
19 /* add b to long a, convert wraparound to max value */
20 #define nowrap_add(a, b) (a = ((a + b) < 0 ? LONG_MAX : (a + b)))
21 
22 /* these probably ought to be generated by makedefs, like LAST_GEM */
23 #define FIRST_GEM DILITHIUM_CRYSTAL
24 #define FIRST_AMULET AMULET_OF_ESP
25 #define LAST_AMULET AMULET_OF_YENDOR
26 
27 struct valuable_data {
28     long count;
29     int typ;
30 };
31 
32 static struct valuable_data
33     gems[LAST_GEM + 1 - FIRST_GEM + 1], /* 1 extra for glass */
34     amulets[LAST_AMULET + 1 - FIRST_AMULET];
35 
36 static struct val_list {
37     struct valuable_data *list;
38     int size;
39 } valuables[] = { { gems, sizeof gems / sizeof *gems },
40                   { amulets, sizeof amulets / sizeof *amulets },
41                   { 0, 0 } };
42 
43 #ifndef NO_SIGNAL
44 STATIC_PTR void FDECL(done_intr, (int));
45 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
46 static void FDECL(done_hangup, (int));
47 #endif
48 #endif
49 STATIC_DCL void FDECL(disclose, (int, BOOLEAN_P));
50 STATIC_DCL void FDECL(get_valuables, (struct obj *));
51 STATIC_DCL void FDECL(sort_valuables, (struct valuable_data *, int));
52 STATIC_DCL void NDECL(done_object_cleanup);
53 STATIC_DCL void FDECL(artifact_score, (struct obj *, BOOLEAN_P, winid));
54 STATIC_DCL void FDECL(really_done, (int)) NORETURN;
55 STATIC_DCL void FDECL(savelife, (int));
56 STATIC_PTR int FDECL(CFDECLSPEC vanqsort_cmp, (const genericptr,
57                                                const genericptr));
58 STATIC_DCL int NDECL(set_vanq_order);
59 STATIC_DCL void FDECL(list_vanquished, (CHAR_P, BOOLEAN_P));
60 STATIC_DCL void FDECL(list_genocided, (CHAR_P, BOOLEAN_P));
61 STATIC_DCL boolean FDECL(should_query_disclose_option, (int, char *));
62 #ifdef DUMPLOG
63 STATIC_DCL void NDECL(dump_plines);
64 #endif
65 STATIC_DCL void FDECL(dump_everything, (int, time_t));
66 STATIC_DCL int NDECL(num_extinct);
67 
68 #if defined(__BEOS__) || defined(MICRO) || defined(OS2)
69 extern void FDECL(nethack_exit, (int));
70 #else
71 #define nethack_exit exit
72 #endif
73 
74 #define done_stopprint program_state.stopprint
75 
76 #ifndef PANICTRACE
77 #define NH_abort NH_abort_
78 #endif
79 
80 #ifdef AMIGA
81 #define NH_abort_() Abort(0)
82 #else
83 #ifdef SYSV
84 #define NH_abort_() (void) abort()
85 #else
86 #ifdef WIN32
87 #define NH_abort_() win32_abort()
88 #else
89 #define NH_abort_() abort()
90 #endif
91 #endif /* !SYSV */
92 #endif /* !AMIGA */
93 
94 #ifdef PANICTRACE
95 #include <errno.h>
96 #ifdef PANICTRACE_LIBC
97 #include <execinfo.h>
98 #endif
99 
100 /* What do we try and in what order?  Tradeoffs:
101  * libc: +no external programs required
102  *        -requires newish libc/glibc
103  *        -requires -rdynamic
104  * gdb:   +gives more detailed information
105  *        +works on more OS versions
106  *        -requires -g, which may preclude -O on some compilers
107  */
108 #ifdef SYSCF
109 #define SYSOPT_PANICTRACE_GDB sysopt.panictrace_gdb
110 #ifdef PANICTRACE_LIBC
111 #define SYSOPT_PANICTRACE_LIBC sysopt.panictrace_libc
112 #else
113 #define SYSOPT_PANICTRACE_LIBC 0
114 #endif
115 #else
116 #define SYSOPT_PANICTRACE_GDB (nh_getenv("NETHACK_USE_GDB") == 0 ? 0 : 2)
117 #ifdef PANICTRACE_LIBC
118 #define SYSOPT_PANICTRACE_LIBC 1
119 #else
120 #define SYSOPT_PANICTRACE_LIBC 0
121 #endif
122 #endif
123 
124 static void NDECL(NH_abort);
125 #ifndef NO_SIGNAL
126 static void FDECL(panictrace_handler, (int));
127 #endif
128 static boolean NDECL(NH_panictrace_libc);
129 static boolean NDECL(NH_panictrace_gdb);
130 
131 #ifndef NO_SIGNAL
132 /* called as signal() handler, so sent at least one arg */
133 /*ARGUSED*/
134 void
panictrace_handler(sig_unused)135 panictrace_handler(sig_unused)
136 int sig_unused UNUSED;
137 {
138 #define SIG_MSG "\nSignal received.\n"
139     int f2;
140 
141     f2 = (int) write(2, SIG_MSG, sizeof SIG_MSG - 1);
142     nhUse(f2);  /* what could we do if write to fd#2 (stderr) fails  */
143     NH_abort(); /* ... and we're already in the process of quitting? */
144 }
145 
146 void
panictrace_setsignals(set)147 panictrace_setsignals(set)
148 boolean set;
149 {
150 #define SETSIGNAL(sig) \
151     (void) signal(sig, set ? (SIG_RET_TYPE) panictrace_handler : SIG_DFL);
152 #ifdef SIGILL
153     SETSIGNAL(SIGILL);
154 #endif
155 #ifdef SIGTRAP
156     SETSIGNAL(SIGTRAP);
157 #endif
158 #ifdef SIGIOT
159     SETSIGNAL(SIGIOT);
160 #endif
161 #ifdef SIGBUS
162     SETSIGNAL(SIGBUS);
163 #endif
164 #ifdef SIGFPE
165     SETSIGNAL(SIGFPE);
166 #endif
167 #ifdef SIGSEGV
168     SETSIGNAL(SIGSEGV);
169 #endif
170 #ifdef SIGSTKFLT
171     SETSIGNAL(SIGSTKFLT);
172 #endif
173 #ifdef SIGSYS
174     SETSIGNAL(SIGSYS);
175 #endif
176 #ifdef SIGEMT
177     SETSIGNAL(SIGEMT);
178 #endif
179 #undef SETSIGNAL
180 }
181 #endif /* NO_SIGNAL */
182 
183 static void
NH_abort()184 NH_abort()
185 {
186     int gdb_prio = SYSOPT_PANICTRACE_GDB;
187     int libc_prio = SYSOPT_PANICTRACE_LIBC;
188     static boolean aborting = FALSE;
189 
190     if (aborting)
191         return;
192     aborting = TRUE;
193 
194 #ifndef VMS
195     if (gdb_prio == libc_prio && gdb_prio > 0)
196         gdb_prio++;
197 
198     if (gdb_prio > libc_prio) {
199         (void) (NH_panictrace_gdb() || (libc_prio && NH_panictrace_libc()));
200     } else {
201         (void) (NH_panictrace_libc() || (gdb_prio && NH_panictrace_gdb()));
202     }
203 
204 #else /* VMS */
205     /* overload otherwise unused priority for debug mode: 1 = show
206        traceback and exit; 2 = show traceback and stay in debugger */
207     /* if (wizard && gdb_prio == 1) gdb_prio = 2; */
208     vms_traceback(gdb_prio);
209     nhUse(libc_prio);
210 
211 #endif /* ?VMS */
212 
213 #ifndef NO_SIGNAL
214     panictrace_setsignals(FALSE);
215 #endif
216     NH_abort_();
217 }
218 
219 static boolean
NH_panictrace_libc()220 NH_panictrace_libc()
221 {
222 #ifdef PANICTRACE_LIBC
223     void *bt[20];
224     size_t count, x;
225     char **info;
226 
227     raw_print("Generating more information you may report:\n");
228     count = backtrace(bt, SIZE(bt));
229     info = backtrace_symbols(bt, count);
230     for (x = 0; x < count; x++) {
231         raw_printf("[%lu] %s", (unsigned long) x, info[x]);
232     }
233     /* free(info);   -- Don't risk it. */
234     return TRUE;
235 #else
236     return FALSE;
237 #endif /* !PANICTRACE_LIBC */
238 }
239 
240 /*
241  *   fooPATH  file system path for foo
242  *   fooVAR   (possibly const) variable containing fooPATH
243  */
244 #ifdef PANICTRACE_GDB
245 #ifdef SYSCF
246 #define GDBVAR sysopt.gdbpath
247 #define GREPVAR sysopt.greppath
248 #else /* SYSCF */
249 #define GDBVAR GDBPATH
250 #define GREPVAR GREPPATH
251 #endif /* SYSCF */
252 #endif /* PANICTRACE_GDB */
253 
254 static boolean
NH_panictrace_gdb()255 NH_panictrace_gdb()
256 {
257 #ifdef PANICTRACE_GDB
258     /* A (more) generic method to get a stack trace - invoke
259      * gdb on ourself. */
260     const char *gdbpath = GDBVAR;
261     const char *greppath = GREPVAR;
262     char buf[BUFSZ];
263     FILE *gdb;
264 
265     if (gdbpath == NULL || gdbpath[0] == 0)
266         return FALSE;
267     if (greppath == NULL || greppath[0] == 0)
268         return FALSE;
269 
270     sprintf(buf, "%s -n -q %s %d 2>&1 | %s '^#'",
271             gdbpath, ARGV0, getpid(), greppath);
272     gdb = popen(buf, "w");
273     if (gdb) {
274         raw_print("Generating more information you may report:\n");
275         fprintf(gdb, "bt\nquit\ny");
276         fflush(gdb);
277         sleep(4); /* ugly */
278         pclose(gdb);
279         return TRUE;
280     } else {
281         return FALSE;
282     }
283 #else
284     return FALSE;
285 #endif /* !PANICTRACE_GDB */
286 }
287 #endif /* PANICTRACE */
288 
289 /*
290  * The order of these needs to match the macros in hack.h.
291  */
292 static NEARDATA const char *deaths[] = {
293     /* the array of death */
294     "died", "choked", "poisoned", "starvation", "drowning", "burning",
295     "dissolving under the heat and pressure", "crushed", "turned to stone",
296     "turned into slime", "genocided", "panic", "trickery", "quit",
297     "escaped", "ascended"
298 };
299 
300 static NEARDATA const char *ends[] = {
301     /* "when you %s" */
302     "died", "choked", "were poisoned",
303     "starved", "drowned", "burned",
304     "dissolved in the lava",
305     "were crushed", "turned to stone",
306     "turned into slime", "were genocided",
307     "panicked", "were tricked", "quit",
308     "escaped", "ascended"
309 };
310 
311 static boolean Schroedingers_cat = FALSE;
312 
313 /*ARGSUSED*/
314 void
done1(sig_unused)315 done1(sig_unused) /* called as signal() handler, so sent at least one arg */
316 int sig_unused UNUSED;
317 {
318 #ifndef NO_SIGNAL
319     (void) signal(SIGINT, SIG_IGN);
320 #endif
321     if (flags.ignintr) {
322 #ifndef NO_SIGNAL
323         (void) signal(SIGINT, (SIG_RET_TYPE) done1);
324 #endif
325         clear_nhwindow(WIN_MESSAGE);
326         curs_on_u();
327         wait_synch();
328         if (multi > 0)
329             nomul(0);
330     } else {
331         (void) done2();
332     }
333 }
334 
335 /* "#quit" command or keyboard interrupt */
336 int
done2()337 done2()
338 {
339     if (iflags.debug_fuzzer)
340         return 0;
341     if (!paranoid_query(ParanoidQuit, "Really quit?")) {
342 #ifndef NO_SIGNAL
343         (void) signal(SIGINT, (SIG_RET_TYPE) done1);
344 #endif
345         clear_nhwindow(WIN_MESSAGE);
346         curs_on_u();
347         wait_synch();
348         if (multi > 0)
349             nomul(0);
350         if (multi == 0) {
351             u.uinvulnerable = FALSE; /* avoid ctrl-C bug -dlc */
352             u.usleep = 0;
353         }
354         return 0;
355     }
356 #if (defined(UNIX) || defined(VMS) || defined(LATTICE))
357     if (wizard) {
358         int c;
359 #ifdef VMS
360         extern int debuggable; /* sys/vms/vmsmisc.c, vmsunix.c */
361 
362         c = !debuggable ? 'n' : ynq("Enter debugger?");
363 #else
364 #ifdef LATTICE
365         c = ynq("Create SnapShot?");
366 #else
367         c = ynq("Dump core?");
368 #endif
369 #endif
370         if (c == 'y') {
371 #ifndef NO_SIGNAL
372             (void) signal(SIGINT, (SIG_RET_TYPE) done1);
373 #endif
374             exit_nhwindows((char *) 0);
375             NH_abort();
376         } else if (c == 'q')
377             done_stopprint++;
378     }
379 #endif
380 #ifndef LINT
381     done(QUIT);
382 #endif
383     return 0;
384 }
385 
386 #ifndef NO_SIGNAL
387 /*ARGSUSED*/
388 STATIC_PTR void
done_intr(sig_unused)389 done_intr(sig_unused) /* called as signal() handler, so sent at least 1 arg */
390 int sig_unused UNUSED;
391 {
392     done_stopprint++;
393     (void) signal(SIGINT, SIG_IGN);
394 #if defined(UNIX) || defined(VMS)
395     (void) signal(SIGQUIT, SIG_IGN);
396 #endif
397     return;
398 }
399 
400 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
401 /* signal() handler */
402 static void
done_hangup(sig)403 done_hangup(sig)
404 int sig;
405 {
406     program_state.done_hup++;
407     sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
408     done_intr(sig);
409     return;
410 }
411 #endif
412 #endif /* NO_SIGNAL */
413 
414 void
done_in_by(mtmp,how)415 done_in_by(mtmp, how)
416 struct monst *mtmp;
417 int how;
418 {
419     char buf[BUFSZ];
420     struct permonst *mptr = mtmp->data,
421                     *champtr = ((mtmp->cham >= LOW_PM)
422                                    ? &mons[mtmp->cham]
423                                    : mptr);
424     boolean distorted = (boolean) (Hallucination && canspotmon(mtmp)),
425             mimicker = (M_AP_TYPE(mtmp) == M_AP_MONSTER),
426             imitator = (mptr != champtr || mimicker);
427 
428     You((how == STONING) ? "turn to stone..." : "die...");
429     mark_synch(); /* flush buffered screen output */
430     buf[0] = '\0';
431     killer.format = KILLED_BY_AN;
432     /* "killed by the high priest of Crom" is okay,
433        "killed by the high priest" alone isn't */
434     if ((mptr->geno & G_UNIQ) != 0 && !(imitator && !mimicker)
435         && !(mptr == &mons[PM_HIGH_PRIEST] && !mtmp->ispriest)) {
436         if (!type_is_pname(mptr))
437             Strcat(buf, "the ");
438         killer.format = KILLED_BY;
439     }
440     /* _the_ <invisible> <distorted> ghost of Dudley */
441     if (mptr == &mons[PM_GHOST] && has_mname(mtmp)) {
442         Strcat(buf, "the ");
443         killer.format = KILLED_BY;
444     }
445     if (mtmp->minvis)
446         Strcat(buf, "invisible ");
447     if (distorted)
448         Strcat(buf, "hallucinogen-distorted ");
449 
450     if (imitator) {
451         char shape[BUFSZ];
452         const char *realnm = champtr->mname, *fakenm = mptr->mname;
453         boolean alt = is_vampshifter(mtmp);
454 
455         if (mimicker) {
456             /* realnm is already correct because champtr==mptr;
457                set up fake mptr for type_is_pname/the_unique_pm */
458             mptr = &mons[mtmp->mappearance];
459             fakenm = mptr->mname;
460         } else if (alt && strstri(realnm, "vampire")
461                    && !strcmp(fakenm, "vampire bat")) {
462             /* special case: use "vampire in bat form" in preference
463                to redundant looking "vampire in vampire bat form" */
464             fakenm = "bat";
465         }
466         /* for the alternate format, always suppress any article;
467            pname and the_unique should also have s_suffix() applied,
468            but vampires don't take on any shapes which warrant that */
469         if (alt || type_is_pname(mptr)) /* no article */
470             Strcpy(shape, fakenm);
471         else if (the_unique_pm(mptr)) /* "the"; don't use the() here */
472             Sprintf(shape, "the %s", fakenm);
473         else /* "a"/"an" */
474             Strcpy(shape, an(fakenm));
475         /* omit "called" to avoid excessive verbosity */
476         Sprintf(eos(buf),
477                 alt ? "%s in %s form"
478                     : mimicker ? "%s disguised as %s"
479                                : "%s imitating %s",
480                 realnm, shape);
481         mptr = mtmp->data; /* reset for mimicker case */
482     } else if (mptr == &mons[PM_GHOST]) {
483         Strcat(buf, "ghost");
484         if (has_mname(mtmp))
485             Sprintf(eos(buf), " of %s", MNAME(mtmp));
486     } else if (mtmp->isshk) {
487         const char *shknm = shkname(mtmp),
488                    *honorific = shkname_is_pname(mtmp) ? ""
489                                    : mtmp->female ? "Ms. " : "Mr. ";
490 
491         Sprintf(eos(buf), "%s%s, the shopkeeper", honorific, shknm);
492         killer.format = KILLED_BY;
493     } else if (mtmp->ispriest || mtmp->isminion) {
494         /* m_monnam() suppresses "the" prefix plus "invisible", and
495            it overrides the effect of Hallucination on priestname() */
496         Strcat(buf, m_monnam(mtmp));
497     } else {
498         Strcat(buf, mptr->mname);
499         if (has_mname(mtmp))
500             Sprintf(eos(buf), " called %s", MNAME(mtmp));
501     }
502 
503     Strcpy(killer.name, buf);
504     /*
505      * Chicken and egg issue:
506      *  Ordinarily Unchanging ought to override something like this,
507      *  but the transformation occurs at death.  With the current code,
508      *  the effectiveness of Unchanging stops first, but a case could
509      *  be made that it should last long enough to prevent undead
510      *  transformation.  (Turning to slime isn't an issue here because
511      *  Unchanging prevents that from happening.)
512      */
513     if (mptr->mlet == S_WRAITH)
514         u.ugrave_arise = PM_WRAITH;
515     else if (mptr->mlet == S_MUMMY && urace.mummynum != NON_PM)
516         u.ugrave_arise = urace.mummynum;
517     else if (mptr->mlet == S_VAMPIRE && Race_if(PM_HUMAN))
518         u.ugrave_arise = PM_VAMPIRE;
519     else if (mptr == &mons[PM_GHOUL])
520         u.ugrave_arise = PM_GHOUL;
521     /* this could happen if a high-end vampire kills the hero
522        when ordinary vampires are genocided; ditto for wraiths */
523     if (u.ugrave_arise >= LOW_PM
524         && (mvitals[u.ugrave_arise].mvflags & G_GENOD))
525         u.ugrave_arise = NON_PM;
526 
527     done(how);
528     return;
529 }
530 
531 /* some special cases for overriding while-helpless reason */
532 static const struct {
533     int why, unmulti;
534     const char *exclude, *include;
535 } death_fixups[] = {
536     /* "petrified by <foo>, while getting stoned" -- "while getting stoned"
537        prevented any last-second recovery, but it was not the cause of
538        "petrified by <foo>" */
539     { STONING, 1, "getting stoned", (char *) 0 },
540     /* "died of starvation, while fainted from lack of food" is accurate
541        but sounds a fairly silly (and doesn't actually appear unless you
542        splice together death and while-helpless from xlogfile) */
543     { STARVING, 0, "fainted from lack of food", "fainted" },
544 };
545 
546 /* clear away while-helpless when the cause of death caused that
547    helplessness (ie, "petrified by <foo> while getting stoned") */
548 STATIC_DCL void
fixup_death(how)549 fixup_death(how)
550 int how;
551 {
552     int i;
553 
554     if (multi_reason) {
555         for (i = 0; i < SIZE(death_fixups); ++i)
556             if (death_fixups[i].why == how
557                 && !strcmp(death_fixups[i].exclude, multi_reason)) {
558                 if (death_fixups[i].include) /* substitute alternate reason */
559                     multi_reason = death_fixups[i].include;
560                 else /* remove the helplessness reason */
561                     multi_reason = (char *) 0;
562                 if (death_fixups[i].unmulti) /* possibly hide helplessness */
563                     multi = 0L;
564                 break;
565             }
566     }
567 }
568 
569 #if defined(WIN32) && !defined(SYSCF)
570 #define NOTIFY_NETHACK_BUGS
571 #endif
572 
573 /*VARARGS1*/
574 void panic
VA_DECL(const char *,str)575 VA_DECL(const char *, str)
576 {
577     VA_START(str);
578     VA_INIT(str, char *);
579 
580     if (program_state.panicking++)
581         NH_abort(); /* avoid loops - this should never happen*/
582 
583     if (iflags.window_inited) {
584         raw_print("\r\nOops...");
585         wait_synch(); /* make sure all pending output gets flushed */
586         exit_nhwindows((char *) 0);
587         iflags.window_inited = 0; /* they're gone; force raw_print()ing */
588     }
589 
590     raw_print(program_state.gameover
591                   ? "Postgame wrapup disrupted."
592                   : !program_state.something_worth_saving
593                         ? "Program initialization has failed."
594                         : "Suddenly, the dungeon collapses.");
595 #ifndef MICRO
596 #ifdef NOTIFY_NETHACK_BUGS
597     if (!wizard)
598         raw_printf("Report the following error to \"%s\" or at \"%s\".",
599                    DEVTEAM_EMAIL, DEVTEAM_URL);
600     else if (program_state.something_worth_saving)
601         raw_print("\nError save file being written.\n");
602 #else /* !NOTIFY_NETHACK_BUGS */
603     if (!wizard) {
604         const char *maybe_rebuild = !program_state.something_worth_saving
605                                      ? "."
606                                      : "\nand it may be possible to rebuild.";
607 
608         if (sysopt.support)
609             raw_printf("To report this error, %s%s", sysopt.support,
610                        maybe_rebuild);
611         else if (sysopt.fmtd_wizard_list) /* formatted SYSCF WIZARDS */
612             raw_printf("To report this error, contact %s%s",
613                        sysopt.fmtd_wizard_list, maybe_rebuild);
614         else
615             raw_printf("Report error to \"%s\"%s", WIZARD_NAME,
616                        maybe_rebuild);
617     }
618 #endif /* ?NOTIFY_NETHACK_BUGS */
619     /* XXX can we move this above the prints?  Then we'd be able to
620      * suppress "it may be possible to rebuild" based on dosave0()
621      * or say it's NOT possible to rebuild. */
622     if (program_state.something_worth_saving && !iflags.debug_fuzzer) {
623         set_error_savefile();
624         if (dosave0()) {
625             /* os/win port specific recover instructions */
626             if (sysopt.recover)
627                 raw_printf("%s", sysopt.recover);
628         }
629     }
630 #endif /* !MICRO */
631     {
632         char buf[BUFSZ];
633 
634 #if !defined(NO_VSNPRINTF)
635         (void) vsnprintf(buf, sizeof buf, str, VA_ARGS);
636 #else
637         Vsprintf(buf, str, VA_ARGS);
638 #endif
639         raw_print(buf);
640         paniclog("panic", buf);
641     }
642 #ifdef WIN32
643     interject(INTERJECT_PANIC);
644 #endif
645 #if defined(UNIX) || defined(VMS) || defined(LATTICE) || defined(WIN32)
646     if (wizard)
647         NH_abort(); /* generate core dump */
648 #endif
649     VA_END();
650     really_done(PANICKED);
651 }
652 
653 STATIC_OVL boolean
should_query_disclose_option(category,defquery)654 should_query_disclose_option(category, defquery)
655 int category;
656 char *defquery;
657 {
658     int idx;
659     char disclose, *dop;
660 
661     *defquery = 'n';
662     if ((dop = index(disclosure_options, category)) != 0) {
663         idx = (int) (dop - disclosure_options);
664         if (idx < 0 || idx >= NUM_DISCLOSURE_OPTIONS) {
665             impossible(
666                    "should_query_disclose_option: bad disclosure index %d %c",
667                        idx, category);
668             *defquery = DISCLOSE_PROMPT_DEFAULT_YES;
669             return TRUE;
670         }
671         disclose = flags.end_disclose[idx];
672         if (disclose == DISCLOSE_YES_WITHOUT_PROMPT) {
673             *defquery = 'y';
674             return FALSE;
675         } else if (disclose == DISCLOSE_SPECIAL_WITHOUT_PROMPT) {
676             *defquery = 'a';
677             return FALSE;
678         } else if (disclose == DISCLOSE_NO_WITHOUT_PROMPT) {
679             *defquery = 'n';
680             return FALSE;
681         } else if (disclose == DISCLOSE_PROMPT_DEFAULT_YES) {
682             *defquery = 'y';
683             return TRUE;
684         } else if (disclose == DISCLOSE_PROMPT_DEFAULT_SPECIAL) {
685             *defquery = 'a';
686             return TRUE;
687         } else {
688             *defquery = 'n';
689             return TRUE;
690         }
691     }
692     impossible("should_query_disclose_option: bad category %c", category);
693     return TRUE;
694 }
695 
696 #ifdef DUMPLOG
697 STATIC_OVL void
dump_plines()698 dump_plines()
699 {
700     int i, j;
701     char buf[BUFSZ], **strp;
702     extern char *saved_plines[];
703     extern unsigned saved_pline_index;
704 
705     Strcpy(buf, " "); /* one space for indentation */
706     putstr(0, 0, "Latest messages:");
707     for (i = 0, j = (int) saved_pline_index; i < DUMPLOG_MSG_COUNT;
708          ++i, j = (j + 1) % DUMPLOG_MSG_COUNT) {
709         strp = &saved_plines[j];
710         if (*strp) {
711             copynchars(&buf[1], *strp, BUFSZ - 1 - 1);
712             putstr(0, 0, buf);
713 #ifdef FREE_ALL_MEMORY
714             free(*strp), *strp = 0;
715 #endif
716         }
717     }
718 }
719 #endif
720 
721 /*ARGSUSED*/
722 STATIC_OVL void
dump_everything(how,when)723 dump_everything(how, when)
724 int how;
725 time_t when; /* date+time at end of game */
726 {
727 #ifdef DUMPLOG
728     char pbuf[BUFSZ], datetimebuf[24]; /* [24]: room for 64-bit bogus value */
729 
730     dump_redirect(TRUE);
731     if (!iflags.in_dumplog)
732         return;
733 
734     init_symbols(); /* revert to default symbol set */
735 
736     /* one line version ID, which includes build date+time;
737        it's conceivable that the game started with a different
738        build date+time or even with an older nethack version,
739        but we only have access to the one it finished under */
740     putstr(0, 0, getversionstring(pbuf));
741     putstr(0, 0, "");
742 
743     /* game start and end date+time to disambiguate version date+time */
744     Strcpy(datetimebuf, yyyymmddhhmmss(ubirthday));
745     Sprintf(pbuf, "Game began %4.4s-%2.2s-%2.2s %2.2s:%2.2s:%2.2s",
746             &datetimebuf[0], &datetimebuf[4], &datetimebuf[6],
747             &datetimebuf[8], &datetimebuf[10], &datetimebuf[12]);
748     Strcpy(datetimebuf, yyyymmddhhmmss(when));
749     Sprintf(eos(pbuf), ", ended %4.4s-%2.2s-%2.2s %2.2s:%2.2s:%2.2s.",
750             &datetimebuf[0], &datetimebuf[4], &datetimebuf[6],
751             &datetimebuf[8], &datetimebuf[10], &datetimebuf[12]);
752     putstr(0, 0, pbuf);
753     putstr(0, 0, "");
754 
755     /* character name and basic role info */
756     Sprintf(pbuf, "%s, %s %s %s %s", plname,
757             aligns[1 - u.ualign.type].adj,
758             genders[flags.female].adj,
759             urace.adj,
760             (flags.female && urole.name.f) ? urole.name.f : urole.name.m);
761     putstr(0, 0, pbuf);
762     putstr(0, 0, "");
763 
764     dump_map();
765     putstr(0, 0, do_statusline1());
766     putstr(0, 0, do_statusline2());
767     putstr(0, 0, "");
768 
769     dump_plines();
770     putstr(0, 0, "");
771     putstr(0, 0, "Inventory:");
772     (void) display_inventory((char *) 0, TRUE);
773     container_contents(invent, TRUE, TRUE, FALSE);
774     enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT),
775                   (how >= PANICKED) ? ENL_GAMEOVERALIVE : ENL_GAMEOVERDEAD);
776     putstr(0, 0, "");
777     list_vanquished('d', FALSE); /* 'd' => 'y' */
778     putstr(0, 0, "");
779     list_genocided('d', FALSE); /* 'd' => 'y' */
780     putstr(0, 0, "");
781     show_conduct((how >= PANICKED) ? 1 : 2);
782     putstr(0, 0, "");
783     show_overview((how >= PANICKED) ? 1 : 2, how);
784     putstr(0, 0, "");
785     dump_redirect(FALSE);
786 #else
787     nhUse(how);
788     nhUse(when);
789 #endif
790 }
791 
792 STATIC_OVL void
disclose(how,taken)793 disclose(how, taken)
794 int how;
795 boolean taken;
796 {
797     char c = '\0', defquery;
798     char qbuf[QBUFSZ];
799     boolean ask = FALSE;
800 
801     if (invent && !done_stopprint) {
802         if (taken)
803             Sprintf(qbuf, "Do you want to see what you had when you %s?",
804                     (how == QUIT) ? "quit" : "died");
805         else
806             Strcpy(qbuf, "Do you want your possessions identified?");
807 
808         ask = should_query_disclose_option('i', &defquery);
809         c = ask ? yn_function(qbuf, ynqchars, defquery) : defquery;
810         if (c == 'y') {
811             /* caller has already ID'd everything */
812             (void) display_inventory((char *) 0, TRUE);
813             container_contents(invent, TRUE, TRUE, FALSE);
814         }
815         if (c == 'q')
816             done_stopprint++;
817     }
818 
819     if (!done_stopprint) {
820         ask = should_query_disclose_option('a', &defquery);
821         c = ask ? yn_function("Do you want to see your attributes?", ynqchars,
822                               defquery)
823                 : defquery;
824         if (c == 'y')
825             enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT),
826                           (how >= PANICKED) ? ENL_GAMEOVERALIVE
827                                             : ENL_GAMEOVERDEAD);
828         if (c == 'q')
829             done_stopprint++;
830     }
831 
832     if (!done_stopprint) {
833         ask = should_query_disclose_option('v', &defquery);
834         list_vanquished(defquery, ask);
835     }
836 
837     if (!done_stopprint) {
838         ask = should_query_disclose_option('g', &defquery);
839         list_genocided(defquery, ask);
840     }
841 
842     if (!done_stopprint) {
843         ask = should_query_disclose_option('c', &defquery);
844         c = ask ? yn_function("Do you want to see your conduct?", ynqchars,
845                               defquery)
846                 : defquery;
847         if (c == 'y')
848             show_conduct((how >= PANICKED) ? 1 : 2);
849         if (c == 'q')
850             done_stopprint++;
851     }
852 
853     if (!done_stopprint) {
854         ask = should_query_disclose_option('o', &defquery);
855         c = ask ? yn_function("Do you want to see the dungeon overview?",
856                               ynqchars, defquery)
857                 : defquery;
858         if (c == 'y')
859             show_overview((how >= PANICKED) ? 1 : 2, how);
860         if (c == 'q')
861             done_stopprint++;
862     }
863 }
864 
865 /* try to get the player back in a viable state after being killed */
866 STATIC_OVL void
savelife(how)867 savelife(how)
868 int how;
869 {
870     int uhpmin = max(2 * u.ulevel, 10);
871 
872     if (u.uhpmax < uhpmin)
873         u.uhpmax = uhpmin;
874     u.uhp = u.uhpmax;
875     if (Upolyd) /* Unchanging, or death which bypasses losing hit points */
876         u.mh = u.mhmax;
877     if (u.uhunger < 500 || how == CHOKING) {
878         init_uhunger();
879     }
880     /* cure impending doom of sickness hero won't have time to fix */
881     if ((Sick & TIMEOUT) == 1L) {
882         make_sick(0L, (char *) 0, FALSE, SICK_ALL);
883     }
884     nomovemsg = "You survived that attempt on your life.";
885     context.move = 0;
886     if (multi > 0)
887         multi = 0;
888     else
889         multi = -1;
890     if (u.utrap && u.utraptype == TT_LAVA)
891         reset_utrap(FALSE);
892     context.botl = 1;
893     u.ugrave_arise = NON_PM;
894     HUnchanging = 0L;
895     curs_on_u();
896     if (!context.mon_moving)
897         endmultishot(FALSE);
898     if (u.uswallow) {
899         /* might drop hero onto a trap that kills her all over again */
900         expels(u.ustuck, u.ustuck->data, TRUE);
901     } else if (u.ustuck) {
902         if (Upolyd && sticks(youmonst.data))
903             You("release %s.", mon_nam(u.ustuck));
904         else
905             pline("%s releases you.", Monnam(u.ustuck));
906         unstuck(u.ustuck);
907     }
908 }
909 
910 /*
911  * Get valuables from the given list.  Revised code: the list always remains
912  * intact.
913  */
914 STATIC_OVL void
get_valuables(list)915 get_valuables(list)
916 struct obj *list; /* inventory or container contents */
917 {
918     register struct obj *obj;
919     register int i;
920 
921     /* find amulets and gems, ignoring all artifacts */
922     for (obj = list; obj; obj = obj->nobj)
923         if (Has_contents(obj)) {
924             get_valuables(obj->cobj);
925         } else if (obj->oartifact) {
926             continue;
927         } else if (obj->oclass == AMULET_CLASS) {
928             i = obj->otyp - FIRST_AMULET;
929             if (!amulets[i].count) {
930                 amulets[i].count = obj->quan;
931                 amulets[i].typ = obj->otyp;
932             } else
933                 amulets[i].count += obj->quan; /* always adds one */
934         } else if (obj->oclass == GEM_CLASS && obj->otyp < LUCKSTONE) {
935             i = min(obj->otyp, LAST_GEM + 1) - FIRST_GEM;
936             if (!gems[i].count) {
937                 gems[i].count = obj->quan;
938                 gems[i].typ = obj->otyp;
939             } else
940                 gems[i].count += obj->quan;
941         }
942     return;
943 }
944 
945 /*
946  *  Sort collected valuables, most frequent to least.  We could just
947  *  as easily use qsort, but we don't care about efficiency here.
948  */
949 STATIC_OVL void
sort_valuables(list,size)950 sort_valuables(list, size)
951 struct valuable_data list[];
952 int size; /* max value is less than 20 */
953 {
954     register int i, j;
955     struct valuable_data ltmp;
956 
957     /* move greater quantities to the front of the list */
958     for (i = 1; i < size; i++) {
959         if (list[i].count == 0)
960             continue;   /* empty slot */
961         ltmp = list[i]; /* structure copy */
962         for (j = i; j > 0; --j)
963             if (list[j - 1].count >= ltmp.count)
964                 break;
965             else {
966                 list[j] = list[j - 1];
967             }
968         list[j] = ltmp;
969     }
970     return;
971 }
972 
973 #if 0
974 /*
975  * odds_and_ends() was used for 3.6.0 and 3.6.1.
976  * Schroedinger's Cat is handled differently as of 3.6.2.
977  */
978 STATIC_DCL boolean FDECL(odds_and_ends, (struct obj *, int));
979 
980 #define CAT_CHECK 2
981 
982 STATIC_OVL boolean
983 odds_and_ends(list, what)
984 struct obj *list;
985 int what;
986 {
987     struct obj *otmp;
988 
989     for (otmp = list; otmp; otmp = otmp->nobj) {
990         switch (what) {
991         case CAT_CHECK: /* Schroedinger's Cat */
992             /* Ascending is deterministic */
993             if (SchroedingersBox(otmp))
994                 return rn2(2);
995             break;
996         }
997         if (Has_contents(otmp))
998             return odds_and_ends(otmp->cobj, what);
999     }
1000     return FALSE;
1001 }
1002 #endif
1003 
1004 /* deal with some objects which may be in an abnormal state at end of game */
1005 STATIC_OVL void
done_object_cleanup()1006 done_object_cleanup()
1007 {
1008     int ox, oy;
1009 
1010     /* might have been killed while using a disposable item, so make sure
1011        it's gone prior to inventory disclosure and creation of bones */
1012     inven_inuse(TRUE);
1013     /*
1014      * Hero can die when throwing an object (by hitting an adjacent
1015      * gas spore, for instance, or being hit by mis-returning Mjollnir),
1016      * or while in transit (from falling down stairs).  If that happens,
1017      * some object(s) might be in limbo rather than on the map or in
1018      * any inventory.  Saving bones with an active light source in limbo
1019      * would trigger an 'object not local' panic.
1020      *
1021      * We used to use dealloc_obj() on thrownobj and kickedobj but
1022      * that keeps them out of bones and could leave uball in a confused
1023      * state (gone but still attached).  Place them on the map but
1024      * bypass flooreffects().  That could lead to minor anomalies in
1025      * bones, like undamaged paper at water or lava locations or piles
1026      * not being knocked down holes, but it seems better to get this
1027      * game over with than risk being tangled up in more and more details.
1028      */
1029     ox = u.ux + u.dx, oy = u.uy + u.dy;
1030     if (!isok(ox, oy) || !accessible(ox, oy))
1031         ox = u.ux, oy = u.uy;
1032     /* put thrown or kicked object on map (for bones); location might
1033        be incorrect (perhaps killed by divine lightning when throwing at
1034        a temple priest?) but this should be better than just vanishing
1035        (fragile stuff should be taken care of before getting here) */
1036     if (thrownobj && thrownobj->where == OBJ_FREE) {
1037         place_object(thrownobj, ox, oy);
1038         stackobj(thrownobj), thrownobj = 0;
1039     }
1040     if (kickedobj && kickedobj->where == OBJ_FREE) {
1041         place_object(kickedobj, ox, oy);
1042         stackobj(kickedobj), kickedobj = 0;
1043     }
1044     /* if Punished hero dies during level change or dies or quits while
1045        swallowed, uball and uchain will be in limbo; put them on floor
1046        so bones will have them and object list cleanup finds them */
1047     if (uchain && uchain->where == OBJ_FREE) {
1048         /* placebc(); */
1049         lift_covet_and_placebc(override_restriction);
1050     }
1051     /* persistent inventory window now obsolete since disclosure uses
1052        a normal popup one; avoids "Bad fruit #n" when saving bones */
1053     if (iflags.perm_invent) {
1054         iflags.perm_invent = FALSE;
1055         update_inventory(); /* make interface notice the change */
1056     }
1057     return;
1058 }
1059 
1060 /* called twice; first to calculate total, then to list relevant items */
1061 STATIC_OVL void
artifact_score(list,counting,endwin)1062 artifact_score(list, counting, endwin)
1063 struct obj *list;
1064 boolean counting; /* true => add up points; false => display them */
1065 winid endwin;
1066 {
1067     char pbuf[BUFSZ];
1068     struct obj *otmp;
1069     long value, points;
1070     short dummy; /* object type returned by artifact_name() */
1071 
1072     for (otmp = list; otmp; otmp = otmp->nobj) {
1073         if (otmp->oartifact || otmp->otyp == BELL_OF_OPENING
1074             || otmp->otyp == SPE_BOOK_OF_THE_DEAD
1075             || otmp->otyp == CANDELABRUM_OF_INVOCATION) {
1076             value = arti_cost(otmp); /* zorkmid value */
1077             points = value * 5 / 2;  /* score value */
1078             if (counting) {
1079                 nowrap_add(u.urexp, points);
1080             } else {
1081                 discover_object(otmp->otyp, TRUE, FALSE);
1082                 otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1;
1083                 /* assumes artifacts don't have quan > 1 */
1084                 Sprintf(pbuf, "%s%s (worth %ld %s and %ld points)",
1085                         the_unique_obj(otmp) ? "The " : "",
1086                         otmp->oartifact ? artifact_name(xname(otmp), &dummy)
1087                                         : OBJ_NAME(objects[otmp->otyp]),
1088                         value, currency(value), points);
1089                 putstr(endwin, 0, pbuf);
1090             }
1091         }
1092         if (Has_contents(otmp))
1093             artifact_score(otmp->cobj, counting, endwin);
1094     }
1095 }
1096 
1097 /* Be careful not to call panic from here! */
1098 void
done(how)1099 done(how)
1100 int how;
1101 {
1102     boolean survive = FALSE;
1103 
1104     if (how == TRICKED) {
1105         if (killer.name[0]) {
1106             paniclog("trickery", killer.name);
1107             killer.name[0] = '\0';
1108         }
1109         if (wizard) {
1110             You("are a very tricky wizard, it seems.");
1111             killer.format = KILLED_BY_AN; /* reset to 0 */
1112             return;
1113         }
1114     }
1115     if (program_state.panicking
1116 #ifdef HANGUPHANDLING
1117         || program_state.done_hup
1118 #endif
1119         ) {
1120         /* skip status update if panicking or disconnected */
1121         context.botl = context.botlx = iflags.time_botl = FALSE;
1122     } else {
1123         /* otherwise force full status update */
1124         context.botlx = TRUE;
1125         bot();
1126     }
1127 
1128     if (iflags.debug_fuzzer) {
1129         if (!(program_state.panicking || how == PANICKED)) {
1130             savelife(how);
1131             /* periodically restore characteristics and lost exp levels
1132                or cure lycanthropy */
1133             if (!rn2(10)) {
1134                 struct obj *potion = mksobj((u.ulycn > LOW_PM && !rn2(3))
1135                                             ? POT_WATER : POT_RESTORE_ABILITY,
1136                                             TRUE, FALSE);
1137 
1138                 bless(potion);
1139                 (void) peffects(potion); /* always -1 for restore ability */
1140                 /* not useup(); we haven't put this potion into inventory */
1141                 obfree(potion, (struct obj *) 0);
1142             }
1143             killer.name[0] = '\0';
1144             killer.format = 0;
1145             return;
1146         }
1147     } else
1148     if (how == ASCENDED || (!killer.name[0] && how == GENOCIDED))
1149         killer.format = NO_KILLER_PREFIX;
1150     /* Avoid killed by "a" burning or "a" starvation */
1151     if (!killer.name[0] && (how == STARVING || how == BURNING))
1152         killer.format = KILLED_BY;
1153     if (!killer.name[0] || how >= PANICKED)
1154         Strcpy(killer.name, deaths[how]);
1155 
1156     if (how < PANICKED) {
1157         u.umortality++;
1158         /* in case caller hasn't already done this */
1159         if (u.uhp != 0 || (Upolyd && u.mh != 0)) {
1160             /* force HP to zero in case it is still positive (some
1161                deaths aren't triggered by loss of hit points), or
1162                negative (-1 is used as a flag in some circumstances
1163                which don't apply when actually dying due to HP loss) */
1164             u.uhp = u.mh = 0;
1165             context.botl = 1;
1166         }
1167     }
1168     if (Lifesaved && (how <= GENOCIDED)) {
1169         pline("But wait...");
1170         makeknown(AMULET_OF_LIFE_SAVING);
1171         Your("medallion %s!", !Blind ? "begins to glow" : "feels warm");
1172         if (how == CHOKING)
1173             You("vomit ...");
1174         You_feel("much better!");
1175         pline_The("medallion crumbles to dust!");
1176         if (uamul)
1177             useup(uamul);
1178 
1179         (void) adjattrib(A_CON, -1, TRUE);
1180         savelife(how);
1181         if (how == GENOCIDED) {
1182             pline("Unfortunately you are still genocided...");
1183         } else {
1184             survive = TRUE;
1185         }
1186     }
1187     /* explore and wizard modes offer player the option to keep playing */
1188     if (!survive && (wizard || discover) && how <= GENOCIDED
1189         && !paranoid_query(ParanoidDie, "Die?")) {
1190         pline("OK, so you don't %s.", (how == CHOKING) ? "choke" : "die");
1191         iflags.last_msg = PLNMSG_OK_DONT_DIE;
1192         savelife(how);
1193         survive = TRUE;
1194     }
1195 
1196     if (survive) {
1197         killer.name[0] = '\0';
1198         killer.format = KILLED_BY_AN; /* reset to 0 */
1199         return;
1200     }
1201     really_done(how);
1202     /*NOTREACHED*/
1203 }
1204 
1205 /* separated from done() in order to specify the __noreturn__ attribute */
1206 STATIC_OVL void
really_done(how)1207 really_done(how)
1208 int how;
1209 {
1210     boolean taken;
1211     char pbuf[BUFSZ];
1212     winid endwin = WIN_ERR;
1213     boolean bones_ok, have_windows = iflags.window_inited;
1214     struct obj *corpse = (struct obj *) 0;
1215     time_t endtime;
1216     long umoney;
1217     long tmp;
1218 
1219     /*
1220      *  The game is now over...
1221      */
1222     program_state.gameover = 1;
1223     /* in case of a subsequent panic(), there's no point trying to save */
1224     program_state.something_worth_saving = 0;
1225 #ifdef HANGUPHANDLING
1226     if (program_state.done_hup)
1227         done_stopprint++;
1228 #endif
1229     /* render vision subsystem inoperative */
1230     iflags.vision_inited = 0;
1231 
1232     /* maybe use up active invent item(s), place thrown/kicked missile,
1233        deal with ball and chain possibly being temporarily off the map */
1234     if (!program_state.panicking)
1235         done_object_cleanup();
1236     /* in case we're panicking; normally cleared by done_object_cleanup() */
1237     iflags.perm_invent = FALSE;
1238 
1239     /* remember time of death here instead of having bones, rip, and
1240        topten figure it out separately and possibly getting different
1241        time or even day if player is slow responding to --More-- */
1242     urealtime.finish_time = endtime = getnow();
1243     urealtime.realtime += (long) (endtime - urealtime.start_timing);
1244     /* collect these for end of game disclosure (not used during play) */
1245     iflags.at_night = night();
1246     iflags.at_midnight = midnight();
1247 
1248     dump_open_log(endtime);
1249     /* Sometimes you die on the first move.  Life's not fair.
1250      * On those rare occasions you get hosed immediately, go out
1251      * smiling... :-)  -3.
1252      */
1253     if (moves <= 1 && how < PANICKED && !done_stopprint)
1254         pline("Do not pass Go.  Do not collect 200 %s.", currency(200L));
1255 
1256     if (have_windows)
1257         wait_synch(); /* flush screen output */
1258 #ifndef NO_SIGNAL
1259     (void) signal(SIGINT, (SIG_RET_TYPE) done_intr);
1260 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
1261     (void) signal(SIGQUIT, (SIG_RET_TYPE) done_intr);
1262     sethanguphandler(done_hangup);
1263 #endif
1264 #endif /* NO_SIGNAL */
1265 
1266     bones_ok = (how < GENOCIDED) && can_make_bones();
1267 
1268     if (bones_ok && launch_in_progress())
1269         force_launch_placement();
1270 
1271     /* maintain ugrave_arise even for !bones_ok */
1272     if (how == PANICKED)
1273         u.ugrave_arise = (NON_PM - 3); /* no corpse, no grave */
1274     else if (how == BURNING || how == DISSOLVED) /* corpse burns up too */
1275         u.ugrave_arise = (NON_PM - 2); /* leave no corpse */
1276     else if (how == STONING)
1277         u.ugrave_arise = (NON_PM - 1); /* statue instead of corpse */
1278     else if (how == TURNED_SLIME
1279              /* it's possible to turn into slime even though green slimes
1280                 have been genocided:  genocide could occur after hero is
1281                 already infected or hero could eat a glob of one created
1282                 before genocide; don't try to arise as one if they're gone */
1283              && !(mvitals[PM_GREEN_SLIME].mvflags & G_GENOD))
1284         u.ugrave_arise = PM_GREEN_SLIME;
1285 
1286     if (how == QUIT) {
1287         killer.format = NO_KILLER_PREFIX;
1288         if (u.uhp < 1) {
1289             how = DIED;
1290             u.umortality++; /* skipped above when how==QUIT */
1291             Strcpy(killer.name, "quit while already on Charon's boat");
1292         }
1293     }
1294     if (how == ESCAPED || how == PANICKED)
1295         killer.format = NO_KILLER_PREFIX;
1296 
1297     fixup_death(how); /* actually, fixup multi_reason */
1298 
1299     if (how != PANICKED) {
1300         boolean silently = done_stopprint ? TRUE : FALSE;
1301 
1302         /* these affect score and/or bones, but avoid them during panic */
1303         taken = paybill((how == ESCAPED) ? -1 : (how != QUIT), silently);
1304         paygd(silently);
1305         clearpriests();
1306     } else
1307         taken = FALSE; /* lint; assert( !bones_ok ); */
1308 
1309     clearlocks();
1310 
1311     if (have_windows)
1312         display_nhwindow(WIN_MESSAGE, FALSE);
1313 
1314     if (how != PANICKED) {
1315         struct obj *obj;
1316 
1317         /*
1318          * This is needed for both inventory disclosure and dumplog.
1319          * Both are optional, so do it once here instead of duplicating
1320          * it in both of those places.
1321          */
1322         for (obj = invent; obj; obj = obj->nobj) {
1323             discover_object(obj->otyp, TRUE, FALSE);
1324             obj->known = obj->bknown = obj->dknown = obj->rknown = 1;
1325             if (Is_container(obj) || obj->otyp == STATUE)
1326                 obj->cknown = obj->lknown = 1;
1327             /* we resolve Schroedinger's cat now in case of both
1328                disclosure and dumplog, where the 50:50 chance for
1329                live cat has to be the same both times */
1330             if (SchroedingersBox(obj)) {
1331                 if (!Schroedingers_cat) {
1332                     /* tell observe_quantum_cat() not to create a cat; if it
1333                        chooses live cat in this situation, it will leave the
1334                        SchroedingersBox flag set (for container_contents()) */
1335                     observe_quantum_cat(obj, FALSE, FALSE);
1336                     if (SchroedingersBox(obj))
1337                         Schroedingers_cat = TRUE;
1338                 } else
1339                     obj->spe = 0; /* ordinary box with cat corpse in it */
1340             }
1341         }
1342 
1343         if (strcmp(flags.end_disclose, "none"))
1344             disclose(how, taken);
1345 
1346         dump_everything(how, endtime);
1347     }
1348 
1349     /* if pets will contribute to score, populate mydogs list now
1350        (bones creation isn't a factor, but pline() messaging is; used to
1351        be done even sooner, but we need it to come after dump_everything()
1352        so that any accompanying pets are still on the map during dump) */
1353     if (how == ESCAPED || how == ASCENDED)
1354         keepdogs(TRUE);
1355 
1356     /* finish_paybill should be called after disclosure but before bones */
1357     if (bones_ok && taken)
1358         finish_paybill();
1359 
1360     /* grave creation should be after disclosure so it doesn't have
1361        this grave in the current level's features for #overview */
1362     if (bones_ok && u.ugrave_arise == NON_PM
1363         && !(mvitals[u.umonnum].mvflags & G_NOCORPSE)) {
1364         int mnum = u.umonnum;
1365 
1366         if (!Upolyd) {
1367             /* Base corpse on race when not poly'd since original u.umonnum
1368                is based on role, and all role monsters are human. */
1369             mnum = (flags.female && urace.femalenum != NON_PM)
1370                        ? urace.femalenum
1371                        : urace.malenum;
1372         }
1373         corpse = mk_named_object(CORPSE, &mons[mnum], u.ux, u.uy, plname);
1374         Sprintf(pbuf, "%s, ", plname);
1375         formatkiller(eos(pbuf), sizeof pbuf - strlen(pbuf), how, TRUE);
1376         make_grave(u.ux, u.uy, pbuf);
1377     }
1378     pbuf[0] = '\0'; /* clear grave text; also lint suppression */
1379 
1380     /* calculate score, before creating bones [container gold] */
1381     {
1382         int deepest = deepest_lev_reached(FALSE);
1383 
1384         umoney = money_cnt(invent);
1385         tmp = u.umoney0;
1386         umoney += hidden_gold(); /* accumulate gold from containers */
1387         tmp = umoney - tmp;      /* net gain */
1388 
1389         if (tmp < 0L)
1390             tmp = 0L;
1391         if (how < PANICKED)
1392             tmp -= tmp / 10L;
1393         tmp += 50L * (long) (deepest - 1);
1394         if (deepest > 20)
1395             tmp += 1000L * (long) ((deepest > 30) ? 10 : deepest - 20);
1396         nowrap_add(u.urexp, tmp);
1397 
1398         /* ascension gives a score bonus iff offering to original deity */
1399         if (how == ASCENDED && u.ualign.type == u.ualignbase[A_ORIGINAL]) {
1400             /* retaining original alignment: score *= 2;
1401                converting, then using helm-of-OA to switch back: *= 1.5 */
1402             tmp = (u.ualignbase[A_CURRENT] == u.ualignbase[A_ORIGINAL])
1403                       ? u.urexp
1404                       : (u.urexp / 2L);
1405             nowrap_add(u.urexp, tmp);
1406         }
1407     }
1408 
1409     if (u.ugrave_arise >= LOW_PM && !done_stopprint) {
1410         /* give this feedback even if bones aren't going to be created,
1411            so that its presence or absence doesn't tip off the player to
1412            new bones or their lack; it might be a lie if makemon fails */
1413         Your("%s as %s...",
1414              (u.ugrave_arise != PM_GREEN_SLIME)
1415                  ? "body rises from the dead"
1416                  : "revenant persists",
1417              an(mons[u.ugrave_arise].mname));
1418         display_nhwindow(WIN_MESSAGE, FALSE);
1419     }
1420 
1421     if (bones_ok) {
1422         if (!wizard || paranoid_query(ParanoidBones, "Save bones?"))
1423             savebones(how, endtime, corpse);
1424         /* corpse may be invalid pointer now so
1425             ensure that it isn't used again */
1426         corpse = (struct obj *) 0;
1427     }
1428 
1429     /* update gold for the rip output, which can't use hidden_gold()
1430        (containers will be gone by then if bones just got saved...) */
1431     done_money = umoney;
1432 
1433     /* clean up unneeded windows */
1434     if (have_windows) {
1435         wait_synch();
1436         free_pickinv_cache(); /* extra persistent window if perm_invent */
1437         if (WIN_INVEN != WIN_ERR) {
1438             destroy_nhwindow(WIN_INVEN),  WIN_INVEN = WIN_ERR;
1439             /* precaution in case any late update_inventory() calls occur */
1440             iflags.perm_invent = 0;
1441         }
1442         display_nhwindow(WIN_MESSAGE, TRUE);
1443         destroy_nhwindow(WIN_MAP),  WIN_MAP = WIN_ERR;
1444         if (WIN_STATUS != WIN_ERR)
1445             destroy_nhwindow(WIN_STATUS),  WIN_STATUS = WIN_ERR;
1446         destroy_nhwindow(WIN_MESSAGE),  WIN_MESSAGE = WIN_ERR;
1447 
1448         if (!done_stopprint || flags.tombstone)
1449             endwin = create_nhwindow(NHW_TEXT);
1450 
1451         if (how < GENOCIDED && flags.tombstone && endwin != WIN_ERR)
1452             outrip(endwin, how, endtime);
1453     } else
1454         done_stopprint = 1; /* just avoid any more output */
1455 
1456 #ifdef DUMPLOG
1457     /* 'how' reasons beyond genocide shouldn't show tombstone;
1458        for normal end of game, genocide doesn't either */
1459     if (how <= GENOCIDED) {
1460         dump_redirect(TRUE);
1461         if (iflags.in_dumplog)
1462             genl_outrip(0, how, endtime);
1463         dump_redirect(FALSE);
1464     }
1465 #endif
1466     if (u.uhave.amulet) {
1467         Strcat(killer.name, " (with the Amulet)");
1468     } else if (how == ESCAPED) {
1469         if (Is_astralevel(&u.uz)) /* offered Amulet to wrong deity */
1470             Strcat(killer.name, " (in celestial disgrace)");
1471         else if (carrying(FAKE_AMULET_OF_YENDOR))
1472             Strcat(killer.name, " (with a fake Amulet)");
1473         /* don't bother counting to see whether it should be plural */
1474     }
1475 
1476     Sprintf(pbuf, "%s %s the %s...", Goodbye(), plname,
1477             (how != ASCENDED)
1478                 ? (const char *) ((flags.female && urole.name.f)
1479                     ? urole.name.f
1480                     : urole.name.m)
1481                 : (const char *) (flags.female ? "Demigoddess" : "Demigod"));
1482     dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1483     dump_forward_putstr(endwin, 0, "", done_stopprint);
1484 
1485     if (how == ESCAPED || how == ASCENDED) {
1486         struct monst *mtmp;
1487         struct obj *otmp;
1488         register struct val_list *val;
1489         register int i;
1490 
1491         for (val = valuables; val->list; val++)
1492             for (i = 0; i < val->size; i++) {
1493                 val->list[i].count = 0L;
1494             }
1495         get_valuables(invent);
1496 
1497         /* add points for collected valuables */
1498         for (val = valuables; val->list; val++)
1499             for (i = 0; i < val->size; i++)
1500                 if (val->list[i].count != 0L) {
1501                     tmp = val->list[i].count
1502                           * (long) objects[val->list[i].typ].oc_cost;
1503                     nowrap_add(u.urexp, tmp);
1504                 }
1505 
1506         /* count the points for artifacts */
1507         artifact_score(invent, TRUE, endwin);
1508 
1509         viz_array[0][0] |= IN_SIGHT; /* need visibility for naming */
1510         mtmp = mydogs;
1511         Strcpy(pbuf, "You");
1512         if (mtmp || Schroedingers_cat) {
1513             while (mtmp) {
1514                 Sprintf(eos(pbuf), " and %s", mon_nam(mtmp));
1515                 if (mtmp->mtame)
1516                     nowrap_add(u.urexp, mtmp->mhp);
1517                 mtmp = mtmp->nmon;
1518             }
1519             /* [it might be more robust to create a housecat and add it to
1520                mydogs; it doesn't have to be placed on the map for that] */
1521             if (Schroedingers_cat) {
1522                 int mhp, m_lev = adj_lev(&mons[PM_HOUSECAT]);
1523 
1524                 mhp = d(m_lev, 8);
1525                 nowrap_add(u.urexp, mhp);
1526                 Strcat(eos(pbuf), " and Schroedinger's cat");
1527             }
1528             dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1529             pbuf[0] = '\0';
1530         } else {
1531             Strcat(pbuf, " ");
1532         }
1533         Sprintf(eos(pbuf), "%s with %ld point%s,",
1534                 how == ASCENDED ? "went to your reward"
1535                                  : "escaped from the dungeon",
1536                 u.urexp, plur(u.urexp));
1537         dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1538 
1539         if (!done_stopprint)
1540             artifact_score(invent, FALSE, endwin); /* list artifacts */
1541 #ifdef DUMPLOG
1542         dump_redirect(TRUE);
1543         if (iflags.in_dumplog)
1544             artifact_score(invent, FALSE, 0);
1545         dump_redirect(FALSE);
1546 #endif
1547 
1548         /* list valuables here */
1549         for (val = valuables; val->list; val++) {
1550             sort_valuables(val->list, val->size);
1551             for (i = 0; i < val->size && !done_stopprint; i++) {
1552                 int typ = val->list[i].typ;
1553                 long count = val->list[i].count;
1554 
1555                 if (count == 0L)
1556                     continue;
1557                 if (objects[typ].oc_class != GEM_CLASS || typ <= LAST_GEM) {
1558                     otmp = mksobj(typ, FALSE, FALSE);
1559                     discover_object(otmp->otyp, TRUE, FALSE);
1560                     otmp->known = 1;  /* for fake amulets */
1561                     otmp->dknown = 1; /* seen it (blindness fix) */
1562                     if (has_oname(otmp))
1563                         free_oname(otmp);
1564                     otmp->quan = count;
1565                     Sprintf(pbuf, "%8ld %s (worth %ld %s),", count,
1566                             xname(otmp), count * (long) objects[typ].oc_cost,
1567                             currency(2L));
1568                     obfree(otmp, (struct obj *) 0);
1569                 } else {
1570                     Sprintf(pbuf, "%8ld worthless piece%s of colored glass,",
1571                             count, plur(count));
1572                 }
1573                 dump_forward_putstr(endwin, 0, pbuf, 0);
1574             }
1575         }
1576 
1577     } else {
1578         /* did not escape or ascend */
1579         if (u.uz.dnum == 0 && u.uz.dlevel <= 0) {
1580             /* level teleported out of the dungeon; `how' is DIED,
1581                due to falling or to "arriving at heaven prematurely" */
1582             Sprintf(pbuf, "You %s beyond the confines of the dungeon",
1583                     (u.uz.dlevel < 0) ? "passed away" : ends[how]);
1584         } else {
1585             /* more conventional demise */
1586             const char *where = dungeons[u.uz.dnum].dname;
1587 
1588             if (Is_astralevel(&u.uz))
1589                 where = "The Astral Plane";
1590             Sprintf(pbuf, "You %s in %s", ends[how], where);
1591             if (!In_endgame(&u.uz) && !Is_knox(&u.uz))
1592                 Sprintf(eos(pbuf), " on dungeon level %d",
1593                         In_quest(&u.uz) ? dunlev(&u.uz) : depth(&u.uz));
1594         }
1595 
1596         Sprintf(eos(pbuf), " with %ld point%s,", u.urexp, plur(u.urexp));
1597         dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1598     }
1599 
1600     Sprintf(pbuf, "and %ld piece%s of gold, after %ld move%s.", umoney,
1601             plur(umoney), moves, plur(moves));
1602     dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1603     Sprintf(pbuf,
1604             "You were level %d with a maximum of %d hit point%s when you %s.",
1605             u.ulevel, u.uhpmax, plur(u.uhpmax), ends[how]);
1606     dump_forward_putstr(endwin, 0, pbuf, done_stopprint);
1607     dump_forward_putstr(endwin, 0, "", done_stopprint);
1608     if (!done_stopprint)
1609         display_nhwindow(endwin, TRUE);
1610     if (endwin != WIN_ERR)
1611         destroy_nhwindow(endwin);
1612 
1613     dump_close_log();
1614     /* "So when I die, the first thing I will see in Heaven is a
1615      * score list?" */
1616     if (have_windows && !iflags.toptenwin)
1617         exit_nhwindows((char *) 0), have_windows = FALSE;
1618     topten(how, endtime);
1619     if (have_windows)
1620         exit_nhwindows((char *) 0);
1621 
1622     if (done_stopprint) {
1623         raw_print("");
1624         raw_print("");
1625     }
1626     nh_terminate(EXIT_SUCCESS);
1627 }
1628 
1629 void
container_contents(list,identified,all_containers,reportempty)1630 container_contents(list, identified, all_containers, reportempty)
1631 struct obj *list;
1632 boolean identified, all_containers, reportempty;
1633 {
1634     register struct obj *box, *obj;
1635     char buf[BUFSZ];
1636     boolean cat, dumping = iflags.in_dumplog;
1637 
1638     for (box = list; box; box = box->nobj) {
1639         if (Is_container(box) || box->otyp == STATUE) {
1640             if (!box->cknown || (identified && !box->lknown)) {
1641                 box->cknown = 1; /* we're looking at the contents now */
1642                 if (identified)
1643                     box->lknown = 1;
1644                 update_inventory();
1645             }
1646             if (box->otyp == BAG_OF_TRICKS) {
1647                 continue; /* wrong type of container */
1648             } else if (box->cobj) {
1649                 winid tmpwin = create_nhwindow(NHW_MENU);
1650                 Loot *sortedcobj, *srtc;
1651                 unsigned sortflags;
1652 
1653                 /* at this stage, the SchroedingerBox() flag is only set
1654                    if the cat inside the box is alive; the box actually
1655                    contains a cat corpse that we'll pretend is not there;
1656                    for dead cat, the flag will be clear and there'll be
1657                    a cat corpse inside the box; either way, inventory
1658                    reports the box as containing "1 item" */
1659                 cat = SchroedingersBox(box);
1660 
1661                 Sprintf(buf, "Contents of %s:", the(xname(box)));
1662                 putstr(tmpwin, 0, buf);
1663                 if (!dumping)
1664                     putstr(tmpwin, 0, "");
1665                 buf[0] = buf[1] = ' '; /* two leading spaces */
1666                 if (box->cobj && !cat) {
1667                     sortflags = (((flags.sortloot == 'l'
1668                                    || flags.sortloot == 'f')
1669                                      ? SORTLOOT_LOOT : 0)
1670                                  | (flags.sortpack ? SORTLOOT_PACK : 0));
1671                     sortedcobj = sortloot(&box->cobj, sortflags, FALSE,
1672                                           (boolean FDECL((*), (OBJ_P))) 0);
1673                     for (srtc = sortedcobj; ((obj = srtc->obj) != 0); ++srtc) {
1674                         if (identified) {
1675                             discover_object(obj->otyp, TRUE, FALSE);
1676                             obj->known = obj->bknown = obj->dknown
1677                                 = obj->rknown = 1;
1678                             if (Is_container(obj) || obj->otyp == STATUE)
1679                                 obj->cknown = obj->lknown = 1;
1680                         }
1681                         Strcpy(&buf[2], doname_with_price(obj));
1682                         putstr(tmpwin, 0, buf);
1683                     }
1684                     unsortloot(&sortedcobj);
1685                 } else if (cat) {
1686                     Strcpy(&buf[2], "Schroedinger's cat!");
1687                     putstr(tmpwin, 0, buf);
1688                 }
1689                 if (dumping)
1690                     putstr(0, 0, "");
1691                 display_nhwindow(tmpwin, TRUE);
1692                 destroy_nhwindow(tmpwin);
1693                 if (all_containers)
1694                     container_contents(box->cobj, identified, TRUE,
1695                                        reportempty);
1696             } else if (reportempty) {
1697                 pline("%s is empty.", upstart(thesimpleoname(box)));
1698                 display_nhwindow(WIN_MESSAGE, FALSE);
1699             }
1700         }
1701         if (!all_containers)
1702             break;
1703     }
1704 }
1705 
1706 /* should be called with either EXIT_SUCCESS or EXIT_FAILURE */
1707 void
nh_terminate(status)1708 nh_terminate(status)
1709 int status;
1710 {
1711     program_state.in_moveloop = 0; /* won't be returning to normal play */
1712 #ifdef MAC
1713     getreturn("to exit");
1714 #endif
1715     /* don't bother to try to release memory if we're in panic mode, to
1716        avoid trouble in case that happens to be due to memory problems */
1717     if (!program_state.panicking) {
1718         freedynamicdata();
1719         dlb_cleanup();
1720     }
1721 
1722 #ifdef VMS
1723     /*
1724      *  This is liable to draw a warning if compiled with gcc, but it's
1725      *  more important to flag panic() -> really_done() -> nh_terminate()
1726      *  as __noreturn__ then to avoid the warning.
1727      */
1728     /* don't call exit() if already executing within an exit handler;
1729        that would cancel any other pending user-mode handlers */
1730     if (program_state.exiting)
1731         return;
1732 #endif
1733     program_state.exiting = 1;
1734     nethack_exit(status);
1735 }
1736 
1737 enum vanq_order_modes {
1738     VANQ_MLVL_MNDX = 0,
1739     VANQ_MSTR_MNDX,
1740     VANQ_ALPHA_SEP,
1741     VANQ_ALPHA_MIX,
1742     VANQ_MCLS_HTOL,
1743     VANQ_MCLS_LTOH,
1744     VANQ_COUNT_H_L,
1745     VANQ_COUNT_L_H,
1746 
1747     NUM_VANQ_ORDER_MODES
1748 };
1749 
1750 static const char *vanqorders[NUM_VANQ_ORDER_MODES] = {
1751     "traditional: by monster level, by internal monster index",
1752     "by monster toughness, by internal monster index",
1753     "alphabetically, first unique monsters, then others",
1754     "alphabetically, unique monsters and others intermixed",
1755     "by monster class, high to low level within class",
1756     "by monster class, low to high level within class",
1757     "by count, high to low, by internal index within tied count",
1758     "by count, low to high, by internal index within tied count",
1759 };
1760 static int vanq_sortmode = VANQ_MLVL_MNDX;
1761 
1762 STATIC_PTR int CFDECLSPEC
vanqsort_cmp(vptr1,vptr2)1763 vanqsort_cmp(vptr1, vptr2)
1764 const genericptr vptr1;
1765 const genericptr vptr2;
1766 {
1767     int indx1 = *(short *) vptr1, indx2 = *(short *) vptr2,
1768         mlev1, mlev2, mstr1, mstr2, uniq1, uniq2, died1, died2, res;
1769     const char *name1, *name2, *punct;
1770     schar mcls1, mcls2;
1771 
1772     switch (vanq_sortmode) {
1773     default:
1774     case VANQ_MLVL_MNDX:
1775         /* sort by monster level */
1776         mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
1777         res = mlev2 - mlev1; /* mlevel high to low */
1778         break;
1779     case VANQ_MSTR_MNDX:
1780         /* sort by monster toughness */
1781         mstr1 = mons[indx1].difficulty, mstr2 = mons[indx2].difficulty;
1782         res = mstr2 - mstr1; /* monstr high to low */
1783         break;
1784     case VANQ_ALPHA_SEP:
1785         uniq1 = ((mons[indx1].geno & G_UNIQ) && indx1 != PM_HIGH_PRIEST);
1786         uniq2 = ((mons[indx2].geno & G_UNIQ) && indx2 != PM_HIGH_PRIEST);
1787         if (uniq1 ^ uniq2) { /* one or other uniq, but not both */
1788             res = uniq2 - uniq1;
1789             break;
1790         } /* else both unique or neither unique */
1791         /*FALLTHRU*/
1792     case VANQ_ALPHA_MIX:
1793         name1 = mons[indx1].mname, name2 = mons[indx2].mname;
1794         res = strcmpi(name1, name2); /* caseblind alhpa, low to high */
1795         break;
1796     case VANQ_MCLS_HTOL:
1797     case VANQ_MCLS_LTOH:
1798         /* mons[].mlet is a small integer, 1..N, of type plain char;
1799            if 'char' happens to be unsigned, (mlet1 - mlet2) would yield
1800            an inappropriate result when mlet2 is greater than mlet1,
1801            so force our copies (mcls1, mcls2) to be signed */
1802         mcls1 = (schar) mons[indx1].mlet, mcls2 = (schar) mons[indx2].mlet;
1803         /* S_ANT through S_ZRUTY correspond to lowercase monster classes,
1804            S_ANGEL through S_ZOMBIE correspond to uppercase, and various
1805            punctuation characters are used for classes beyond those */
1806         if (mcls1 > S_ZOMBIE && mcls2 > S_ZOMBIE) {
1807             /* force a specific order to the punctuation classes that's
1808                different from the internal order;
1809                internal order is ok if neither or just one is punctuation
1810                since letters have lower values so come out before punct */
1811             static const char punctclasses[] = {
1812                 S_LIZARD, S_EEL, S_GOLEM, S_GHOST, S_DEMON, S_HUMAN, '\0'
1813             };
1814 
1815             if ((punct = index(punctclasses, mcls1)) != 0)
1816                 mcls1 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
1817             if ((punct = index(punctclasses, mcls2)) != 0)
1818                 mcls2 = (schar) (S_ZOMBIE + 1 + (int) (punct - punctclasses));
1819         }
1820         res = mcls1 - mcls2; /* class */
1821         if (res == 0) {
1822             mlev1 = mons[indx1].mlevel, mlev2 = mons[indx2].mlevel;
1823             res = mlev1 - mlev2; /* mlevel low to high */
1824             if (vanq_sortmode == VANQ_MCLS_HTOL)
1825                 res = -res; /* mlevel high to low */
1826         }
1827         break;
1828     case VANQ_COUNT_H_L:
1829     case VANQ_COUNT_L_H:
1830         died1 = mvitals[indx1].died, died2 = mvitals[indx2].died;
1831         res = died2 - died1; /* dead count high to low */
1832         if (vanq_sortmode == VANQ_COUNT_L_H)
1833             res = -res; /* dead count low to high */
1834         break;
1835     }
1836     /* tiebreaker: internal mons[] index */
1837     if (res == 0)
1838         res = indx1 - indx2; /* mndx low to high */
1839     return res;
1840 }
1841 
1842 /* returns -1 if cancelled via ESC */
1843 STATIC_OVL int
set_vanq_order()1844 set_vanq_order()
1845 {
1846     winid tmpwin;
1847     menu_item *selected;
1848     anything any;
1849     int i, n, choice;
1850 
1851     tmpwin = create_nhwindow(NHW_MENU);
1852     start_menu(tmpwin);
1853     any = zeroany; /* zero out all bits */
1854     for (i = 0; i < SIZE(vanqorders); i++) {
1855         if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */
1856             continue;
1857         any.a_int = i + 1;
1858         add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, vanqorders[i],
1859                  (i == vanq_sortmode) ? MENU_SELECTED : MENU_UNSELECTED);
1860     }
1861     end_menu(tmpwin, "Sort order for vanquished monster counts");
1862 
1863     n = select_menu(tmpwin, PICK_ONE, &selected);
1864     destroy_nhwindow(tmpwin);
1865     if (n > 0) {
1866         choice = selected[0].item.a_int - 1;
1867         /* skip preselected entry if we have more than one item chosen */
1868         if (n > 1 && choice == vanq_sortmode)
1869             choice = selected[1].item.a_int - 1;
1870         free((genericptr_t) selected);
1871         vanq_sortmode = choice;
1872     }
1873     return (n < 0) ? -1 : vanq_sortmode;
1874 }
1875 
1876 /* #vanquished command */
1877 int
dovanquished()1878 dovanquished()
1879 {
1880     list_vanquished('a', FALSE);
1881     return 0;
1882 }
1883 
1884 /* high priests aren't unique but are flagged as such to simplify something */
1885 #define UniqCritterIndx(mndx) ((mons[mndx].geno & G_UNIQ) \
1886                                && mndx != PM_HIGH_PRIEST)
1887 
1888 STATIC_OVL void
list_vanquished(defquery,ask)1889 list_vanquished(defquery, ask)
1890 char defquery;
1891 boolean ask;
1892 {
1893     register int i;
1894     int pfx, nkilled;
1895     unsigned ntypes, ni;
1896     long total_killed = 0L;
1897     winid klwin;
1898     short mindx[NUMMONS];
1899     char c, buf[BUFSZ], buftoo[BUFSZ];
1900     boolean dumping; /* for DUMPLOG; doesn't need to be conditional */
1901 
1902     dumping = (defquery == 'd');
1903     if (dumping)
1904         defquery = 'y';
1905 
1906     /* get totals first */
1907     ntypes = 0;
1908     for (i = LOW_PM; i < NUMMONS; i++) {
1909         if ((nkilled = (int) mvitals[i].died) == 0)
1910             continue;
1911         mindx[ntypes++] = i;
1912         total_killed += (long) nkilled;
1913     }
1914 
1915     /* vanquished creatures list;
1916      * includes all dead monsters, not just those killed by the player
1917      */
1918     if (ntypes != 0) {
1919         char mlet, prev_mlet = 0; /* used as small integer, not character */
1920         boolean class_header, uniq_header, was_uniq = FALSE;
1921 
1922         c = ask ? yn_function(
1923                             "Do you want an account of creatures vanquished?",
1924                               ynaqchars, defquery)
1925                 : defquery;
1926         if (c == 'q')
1927             done_stopprint++;
1928         if (c == 'y' || c == 'a') {
1929             if (c == 'a') { /* ask player to choose sort order */
1930                 /* choose value for vanq_sortmode via menu; ESC cancels list
1931                    of vanquished monsters but does not set 'done_stopprint' */
1932                 if (set_vanq_order() < 0)
1933                     return;
1934             }
1935             uniq_header = (vanq_sortmode == VANQ_ALPHA_SEP);
1936             class_header = (vanq_sortmode == VANQ_MCLS_LTOH
1937                             || vanq_sortmode == VANQ_MCLS_HTOL);
1938 
1939             klwin = create_nhwindow(NHW_MENU);
1940             putstr(klwin, 0, "Vanquished creatures:");
1941             if (!dumping)
1942                 putstr(klwin, 0, "");
1943 
1944             qsort((genericptr_t) mindx, ntypes, sizeof *mindx, vanqsort_cmp);
1945             for (ni = 0; ni < ntypes; ni++) {
1946                 i = mindx[ni];
1947                 nkilled = mvitals[i].died;
1948                 mlet = mons[i].mlet;
1949                 if (class_header && mlet != prev_mlet) {
1950                     Strcpy(buf, def_monsyms[(int) mlet].explain);
1951                     putstr(klwin, ask ? 0 : iflags.menu_headings,
1952                            upstart(buf));
1953                     prev_mlet = mlet;
1954                 }
1955                 if (UniqCritterIndx(i)) {
1956                     Sprintf(buf, "%s%s",
1957                             !type_is_pname(&mons[i]) ? "the " : "",
1958                             mons[i].mname);
1959                     if (nkilled > 1) {
1960                         switch (nkilled) {
1961                         case 2:
1962                             Sprintf(eos(buf), " (twice)");
1963                             break;
1964                         case 3:
1965                             Sprintf(eos(buf), " (thrice)");
1966                             break;
1967                         default:
1968                             Sprintf(eos(buf), " (%d times)", nkilled);
1969                             break;
1970                         }
1971                     }
1972                     was_uniq = TRUE;
1973                 } else {
1974                     if (uniq_header && was_uniq) {
1975                         putstr(klwin, 0, "");
1976                         was_uniq = FALSE;
1977                     }
1978                     /* trolls or undead might have come back,
1979                        but we don't keep track of that */
1980                     if (nkilled == 1)
1981                         Strcpy(buf, an(mons[i].mname));
1982                     else
1983                         Sprintf(buf, "%3d %s", nkilled,
1984                                 makeplural(mons[i].mname));
1985                 }
1986                 /* number of leading spaces to match 3 digit prefix */
1987                 pfx = !strncmpi(buf, "the ", 3) ? 0
1988                       : !strncmpi(buf, "an ", 3) ? 1
1989                         : !strncmpi(buf, "a ", 2) ? 2
1990                           : !digit(buf[2]) ? 4 : 0;
1991                 if (class_header)
1992                     ++pfx;
1993                 Sprintf(buftoo, "%*s%s", pfx, "", buf);
1994                 putstr(klwin, 0, buftoo);
1995             }
1996             /*
1997              * if (Hallucination)
1998              *     putstr(klwin, 0, "and a partridge in a pear tree");
1999              */
2000             if (ntypes > 1) {
2001                 if (!dumping)
2002                     putstr(klwin, 0, "");
2003                 Sprintf(buf, "%ld creatures vanquished.", total_killed);
2004                 putstr(klwin, 0, buf);
2005             }
2006             display_nhwindow(klwin, TRUE);
2007             destroy_nhwindow(klwin);
2008         }
2009     } else if (defquery == 'a') {
2010         /* #dovanquished rather than final disclosure, so pline() is ok */
2011         pline("No creatures have been vanquished.");
2012 #ifdef DUMPLOG
2013     } else if (dumping) {
2014         putstr(0, 0, "No creatures were vanquished."); /* not pline() */
2015 #endif
2016     }
2017 }
2018 
2019 /* number of monster species which have been genocided */
2020 int
num_genocides()2021 num_genocides()
2022 {
2023     int i, n = 0;
2024 
2025     for (i = LOW_PM; i < NUMMONS; ++i) {
2026         if (mvitals[i].mvflags & G_GENOD) {
2027             ++n;
2028             if (UniqCritterIndx(i))
2029                 impossible("unique creature '%d: %s' genocided?",
2030                            i, mons[i].mname);
2031         }
2032     }
2033     return n;
2034 }
2035 
2036 STATIC_OVL int
num_extinct()2037 num_extinct()
2038 {
2039     int i, n = 0;
2040 
2041     for (i = LOW_PM; i < NUMMONS; ++i) {
2042         if (UniqCritterIndx(i))
2043             continue;
2044         if ((mvitals[i].mvflags & G_GONE) == G_EXTINCT)
2045             ++n;
2046     }
2047     return n;
2048 }
2049 
2050 STATIC_OVL void
list_genocided(defquery,ask)2051 list_genocided(defquery, ask)
2052 char defquery;
2053 boolean ask;
2054 {
2055     register int i;
2056     int ngenocided, nextinct;
2057     char c;
2058     winid klwin;
2059     char buf[BUFSZ];
2060     boolean dumping; /* for DUMPLOG; doesn't need to be conditional */
2061 
2062     dumping = (defquery == 'd');
2063     if (dumping)
2064         defquery = 'y';
2065 
2066     ngenocided = num_genocides();
2067     nextinct = num_extinct();
2068 
2069     /* genocided or extinct species list */
2070     if (ngenocided != 0 || nextinct != 0) {
2071         Sprintf(buf, "Do you want a list of %sspecies%s%s?",
2072                 (nextinct && !ngenocided) ? "extinct " : "",
2073                 (ngenocided) ? " genocided" : "",
2074                 (nextinct && ngenocided) ? " and extinct" : "");
2075         c = ask ? yn_function(buf, ynqchars, defquery) : defquery;
2076         if (c == 'q')
2077             done_stopprint++;
2078         if (c == 'y') {
2079             klwin = create_nhwindow(NHW_MENU);
2080             Sprintf(buf, "%s%s species:",
2081                     (ngenocided) ? "Genocided" : "Extinct",
2082                     (nextinct && ngenocided) ? " or extinct" : "");
2083             putstr(klwin, 0, buf);
2084             if (!dumping)
2085                 putstr(klwin, 0, "");
2086 
2087             for (i = LOW_PM; i < NUMMONS; i++) {
2088                 /* uniques can't be genocided but can become extinct;
2089                    however, they're never reported as extinct, so skip them */
2090                 if (UniqCritterIndx(i))
2091                     continue;
2092                 if (mvitals[i].mvflags & G_GONE) {
2093                     Sprintf(buf, " %s", makeplural(mons[i].mname));
2094                     /*
2095                      * "Extinct" is unfortunate terminology.  A species
2096                      * is marked extinct when its birth limit is reached,
2097                      * but there might be members of the species still
2098                      * alive, contradicting the meaning of the word.
2099                      */
2100                     if ((mvitals[i].mvflags & G_GONE) == G_EXTINCT)
2101                         Strcat(buf, " (extinct)");
2102                     putstr(klwin, 0, buf);
2103                 }
2104             }
2105             if (!dumping)
2106                 putstr(klwin, 0, "");
2107             if (ngenocided > 0) {
2108                 Sprintf(buf, "%d species genocided.", ngenocided);
2109                 putstr(klwin, 0, buf);
2110             }
2111             if (nextinct > 0) {
2112                 Sprintf(buf, "%d species extinct.", nextinct);
2113                 putstr(klwin, 0, buf);
2114             }
2115 
2116             display_nhwindow(klwin, TRUE);
2117             destroy_nhwindow(klwin);
2118         }
2119 #ifdef DUMPLOG
2120     } else if (dumping) {
2121         putstr(0, 0, "No species were genocided or became extinct.");
2122 #endif
2123     }
2124 }
2125 
2126 /* set a delayed killer, ensure non-delayed killer is cleared out */
2127 void
delayed_killer(id,format,killername)2128 delayed_killer(id, format, killername)
2129 int id;
2130 int format;
2131 const char *killername;
2132 {
2133     struct kinfo *k = find_delayed_killer(id);
2134 
2135     if (!k) {
2136         /* no match, add a new delayed killer to the list */
2137         k = (struct kinfo *) alloc(sizeof (struct kinfo));
2138         (void) memset((genericptr_t) k, 0, sizeof (struct kinfo));
2139         k->id = id;
2140         k->next = killer.next;
2141         killer.next = k;
2142     }
2143 
2144     k->format = format;
2145     Strcpy(k->name, killername ? killername : "");
2146     killer.name[0] = 0;
2147 }
2148 
2149 struct kinfo *
find_delayed_killer(id)2150 find_delayed_killer(id)
2151 int id;
2152 {
2153     struct kinfo *k;
2154 
2155     for (k = killer.next; k != (struct kinfo *) 0; k = k->next) {
2156         if (k->id == id)
2157             break;
2158     }
2159     return k;
2160 }
2161 
2162 void
dealloc_killer(kptr)2163 dealloc_killer(kptr)
2164 struct kinfo *kptr;
2165 {
2166     struct kinfo *prev = &killer, *k;
2167 
2168     if (kptr == (struct kinfo *) 0)
2169         return;
2170     for (k = killer.next; k != (struct kinfo *) 0; k = k->next) {
2171         if (k == kptr)
2172             break;
2173         prev = k;
2174     }
2175 
2176     if (k == (struct kinfo *) 0) {
2177         impossible("dealloc_killer (#%d) not on list", kptr->id);
2178     } else {
2179         prev->next = k->next;
2180         free((genericptr_t) k);
2181         debugpline1("freed delayed killer #%d", kptr->id);
2182     }
2183 }
2184 
2185 void
save_killers(fd,mode)2186 save_killers(fd, mode)
2187 int fd;
2188 int mode;
2189 {
2190     struct kinfo *kptr;
2191 
2192     if (perform_bwrite(mode)) {
2193         for (kptr = &killer; kptr != (struct kinfo *) 0; kptr = kptr->next) {
2194             bwrite(fd, (genericptr_t) kptr, sizeof (struct kinfo));
2195         }
2196     }
2197     if (release_data(mode)) {
2198         while (killer.next) {
2199             kptr = killer.next->next;
2200             free((genericptr_t) killer.next);
2201             killer.next = kptr;
2202         }
2203     }
2204 }
2205 
2206 void
restore_killers(fd)2207 restore_killers(fd)
2208 int fd;
2209 {
2210     struct kinfo *kptr;
2211 
2212     for (kptr = &killer; kptr != (struct kinfo *) 0; kptr = kptr->next) {
2213         mread(fd, (genericptr_t) kptr, sizeof (struct kinfo));
2214         if (kptr->next) {
2215             kptr->next = (struct kinfo *) alloc(sizeof (struct kinfo));
2216         }
2217     }
2218 }
2219 
2220 static int
wordcount(p)2221 wordcount(p)
2222 char *p;
2223 {
2224     int words = 0;
2225 
2226     while (*p) {
2227         while (*p && isspace((uchar) *p))
2228             p++;
2229         if (*p)
2230             words++;
2231         while (*p && !isspace((uchar) *p))
2232             p++;
2233     }
2234     return words;
2235 }
2236 
2237 static void
bel_copy1(inp,out)2238 bel_copy1(inp, out)
2239 char **inp, *out;
2240 {
2241     char *in = *inp;
2242 
2243     out += strlen(out); /* eos() */
2244     while (*in && isspace((uchar) *in))
2245         in++;
2246     while (*in && !isspace((uchar) *in))
2247         *out++ = *in++;
2248     *out = '\0';
2249     *inp = in;
2250 }
2251 
2252 char *
build_english_list(in)2253 build_english_list(in)
2254 char *in;
2255 {
2256     char *out, *p = in;
2257     int len = (int) strlen(p), words = wordcount(p);
2258 
2259     /* +3: " or " - " "; +(words - 1): (N-1)*(", " - " ") */
2260     if (words > 1)
2261         len += 3 + (words - 1);
2262     out = (char *) alloc(len + 1);
2263     *out = '\0'; /* bel_copy1() appends */
2264 
2265     switch (words) {
2266     case 0:
2267         impossible("no words in list");
2268         break;
2269     case 1:
2270         /* "single" */
2271         bel_copy1(&p, out);
2272         break;
2273     default:
2274         if (words == 2) {
2275             /* "first or second" */
2276             bel_copy1(&p, out);
2277             Strcat(out, " ");
2278         } else {
2279             /* "first, second, or third */
2280             do {
2281                 bel_copy1(&p, out);
2282                 Strcat(out, ", ");
2283             } while (--words > 1);
2284         }
2285         Strcat(out, "or ");
2286         bel_copy1(&p, out);
2287         break;
2288     }
2289     return out;
2290 }
2291 
2292 /*end.c*/
2293