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