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