Also pass raw printed texts to the msghandler
[aNetHack.git] / src / topten.c
blob84146e9ad47373382ecd1a173a3075ddccf9ff93
1 /* NetHack 3.6 topten.c $NHDT-Date: 1450451497 2015/12/18 15:11:37 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.44 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 #include "hack.h"
6 #include "dlb.h"
7 #ifdef SHORT_FILENAMES
8 #include "patchlev.h"
9 #else
10 #include "patchlevel.h"
11 #endif
13 #ifdef VMS
14 /* We don't want to rewrite the whole file, because that entails
15 creating a new version which requires that the old one be deletable. */
16 #define UPDATE_RECORD_IN_PLACE
17 #endif
20 * Updating in place can leave junk at the end of the file in some
21 * circumstances (if it shrinks and the O.S. doesn't have a straightforward
22 * way to truncate it). The trailing junk is harmless and the code
23 * which reads the scores will ignore it.
25 #ifdef UPDATE_RECORD_IN_PLACE
26 static long final_fpos;
27 #endif
29 #define done_stopprint program_state.stopprint
31 #define newttentry() (struct toptenentry *) alloc(sizeof (struct toptenentry))
32 #define dealloc_ttentry(ttent) free((genericptr_t) (ttent))
33 #define NAMSZ 10
34 #define DTHSZ 100
35 #define ROLESZ 3
37 struct toptenentry {
38 struct toptenentry *tt_next;
39 #ifdef UPDATE_RECORD_IN_PLACE
40 long fpos;
41 #endif
42 long points;
43 int deathdnum, deathlev;
44 int maxlvl, hp, maxhp, deaths;
45 int ver_major, ver_minor, patchlevel;
46 long deathdate, birthdate;
47 int uid;
48 char plrole[ROLESZ + 1];
49 char plrace[ROLESZ + 1];
50 char plgend[ROLESZ + 1];
51 char plalign[ROLESZ + 1];
52 char name[NAMSZ + 1];
53 char death[DTHSZ + 1];
54 } * tt_head;
55 /* size big enough to read in all the string fields at once; includes
56 room for separating space or trailing newline plus string terminator */
57 #define SCANBUFSZ (4 * (ROLESZ + 1) + (NAMSZ + 1) + (DTHSZ + 1) + 1)
59 STATIC_DCL void FDECL(topten_print, (const char *));
60 STATIC_DCL void FDECL(topten_print_bold, (const char *));
61 STATIC_DCL xchar FDECL(observable_depth, (d_level *));
62 STATIC_DCL void NDECL(outheader);
63 STATIC_DCL void FDECL(outentry, (int, struct toptenentry *, BOOLEAN_P));
64 STATIC_DCL void FDECL(discardexcess, (FILE *));
65 STATIC_DCL void FDECL(readentry, (FILE *, struct toptenentry *));
66 STATIC_DCL void FDECL(writeentry, (FILE *, struct toptenentry *));
67 #ifdef XLOGFILE
68 STATIC_DCL void FDECL(writexlentry, (FILE *, struct toptenentry *, int));
69 STATIC_DCL long NDECL(encodexlogflags);
70 STATIC_DCL long NDECL(encodeconduct);
71 STATIC_DCL long NDECL(encodeachieve);
72 #endif
73 STATIC_DCL void FDECL(free_ttlist, (struct toptenentry *));
74 STATIC_DCL int FDECL(classmon, (char *, BOOLEAN_P));
75 STATIC_DCL int FDECL(score_wanted, (BOOLEAN_P, int, struct toptenentry *, int,
76 const char **, int));
77 #ifdef NO_SCAN_BRACK
78 STATIC_DCL void FDECL(nsb_mung_line, (char *));
79 STATIC_DCL void FDECL(nsb_unmung_line, (char *));
80 #endif
82 static winid toptenwin = WIN_ERR;
84 /* "killed by",&c ["an"] 'killer.name' */
85 void
86 formatkiller(buf, siz, how, incl_helpless)
87 char *buf;
88 unsigned siz;
89 int how;
90 boolean incl_helpless;
92 static NEARDATA const char *const killed_by_prefix[] = {
93 /* DIED, CHOKING, POISONING, STARVING, */
94 "killed by ", "choked on ", "poisoned by ", "died of ",
95 /* DROWNING, BURNING, DISSOLVED, CRUSHING, */
96 "drowned in ", "burned by ", "dissolved in ", "crushed to death by ",
97 /* STONING, TURNED_SLIME, GENOCIDED, */
98 "petrified by ", "turned to slime by ", "killed by ",
99 /* PANICKED, TRICKED, QUIT, ESCAPED, ASCENDED */
100 "", "", "", "", ""
102 unsigned l;
103 char c, *kname = killer.name;
105 buf[0] = '\0'; /* lint suppression */
106 switch (killer.format) {
107 default:
108 impossible("bad killer format? (%d)", killer.format);
109 /*FALLTHRU*/
110 case NO_KILLER_PREFIX:
111 break;
112 case KILLED_BY_AN:
113 kname = an(kname);
114 /*FALLTHRU*/
115 case KILLED_BY:
116 (void) strncat(buf, killed_by_prefix[how], siz - 1);
117 l = strlen(buf);
118 buf += l, siz -= l;
119 break;
121 /* Copy kname into buf[].
122 * Object names and named fruit have already been sanitized, but
123 * monsters can have "called 'arbitrary text'" attached to them,
124 * so make sure that that text can't confuse field splitting when
125 * record, logfile, or xlogfile is re-read at some later point.
127 while (--siz > 0) {
128 c = *kname++;
129 if (c == ',')
130 c = ';';
131 /* 'xlogfile' doesn't really need protection for '=', but
132 fixrecord.awk for corrupted 3.6.0 'record' does (only
133 if using xlogfile rather than logfile to repair record) */
134 else if (c == '=')
135 c = '_';
136 /* tab is not possible due to use of mungspaces() when naming;
137 it would disrupt xlogfile parsing if it were present */
138 else if (c == '\t')
139 c = ' ';
140 *buf++ = c;
142 *buf = '\0';
144 if (incl_helpless && multi) {
145 /* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */
146 if (multi_reason && strlen(multi_reason) + sizeof ", while " <= siz)
147 Sprintf(buf, ", while %s", multi_reason);
148 /* either multi_reason wasn't specified or wouldn't fit */
149 else if (sizeof ", while helpless" <= siz)
150 Strcpy(buf, ", while helpless");
151 /* else extra death info won't fit, so leave it out */
155 STATIC_OVL void
156 topten_print(x)
157 const char *x;
159 if (toptenwin == WIN_ERR)
160 raw_print(x);
161 else
162 putstr(toptenwin, ATR_NONE, x);
165 STATIC_OVL void
166 topten_print_bold(x)
167 const char *x;
169 if (toptenwin == WIN_ERR)
170 raw_print_bold(x);
171 else
172 putstr(toptenwin, ATR_BOLD, x);
175 STATIC_OVL xchar
176 observable_depth(lev)
177 d_level *lev;
179 #if 0
180 /* if we ever randomize the order of the elemental planes, we
181 must use a constant external representation in the record file */
182 if (In_endgame(lev)) {
183 if (Is_astralevel(lev))
184 return -5;
185 else if (Is_waterlevel(lev))
186 return -4;
187 else if (Is_firelevel(lev))
188 return -3;
189 else if (Is_airlevel(lev))
190 return -2;
191 else if (Is_earthlevel(lev))
192 return -1;
193 else
194 return 0; /* ? */
195 } else
196 #endif
197 return depth(lev);
200 /* throw away characters until current record has been entirely consumed */
201 STATIC_OVL void
202 discardexcess(rfile)
203 FILE *rfile;
205 int c;
207 do {
208 c = fgetc(rfile);
209 } while (c != '\n' && c != EOF);
212 STATIC_OVL void
213 readentry(rfile, tt)
214 FILE *rfile;
215 struct toptenentry *tt;
217 char inbuf[SCANBUFSZ], s1[SCANBUFSZ], s2[SCANBUFSZ], s3[SCANBUFSZ],
218 s4[SCANBUFSZ], s5[SCANBUFSZ], s6[SCANBUFSZ];
220 #ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
221 static const char fmt[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c";
222 static const char fmt32[] = "%c%c %s %s%*c";
223 static const char fmt33[] = "%s %s %s %s %s %s%*c";
224 #else
225 static const char fmt[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
226 static const char fmt32[] = "%c%c %[^,],%[^\n]%*c";
227 static const char fmt33[] = "%s %s %s %s %[^,],%[^\n]%*c";
228 #endif
230 #ifdef UPDATE_RECORD_IN_PLACE
231 /* note: input below must read the record's terminating newline */
232 final_fpos = tt->fpos = ftell(rfile);
233 #endif
234 #define TTFIELDS 13
235 if (fscanf(rfile, fmt, &tt->ver_major, &tt->ver_minor, &tt->patchlevel,
236 &tt->points, &tt->deathdnum, &tt->deathlev, &tt->maxlvl,
237 &tt->hp, &tt->maxhp, &tt->deaths, &tt->deathdate,
238 &tt->birthdate, &tt->uid) != TTFIELDS) {
239 #undef TTFIELDS
240 tt->points = 0;
241 discardexcess(rfile);
242 } else {
243 /* load remainder of record into a local buffer;
244 this imposes an implicit length limit of SCANBUFSZ
245 on every string field extracted from the buffer */
246 if (!fgets(inbuf, sizeof inbuf, rfile)) {
247 /* sscanf will fail and tt->points will be set to 0 */
248 *inbuf = '\0';
249 } else if (!index(inbuf, '\n')) {
250 Strcpy(&inbuf[sizeof inbuf - 2], "\n");
251 discardexcess(rfile);
253 /* Check for backwards compatibility */
254 if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3)) {
255 int i;
257 if (sscanf(inbuf, fmt32, tt->plrole, tt->plgend, s1, s2) == 4) {
258 tt->plrole[1] = tt->plgend[1] = '\0'; /* read via %c */
259 copynchars(tt->name, s1, (int) (sizeof tt->name) - 1);
260 copynchars(tt->death, s2, (int) (sizeof tt->death) - 1);
261 } else
262 tt->points = 0;
263 tt->plrole[1] = '\0';
264 if ((i = str2role(tt->plrole)) >= 0)
265 Strcpy(tt->plrole, roles[i].filecode);
266 Strcpy(tt->plrace, "?");
267 Strcpy(tt->plgend, (tt->plgend[0] == 'M') ? "Mal" : "Fem");
268 Strcpy(tt->plalign, "?");
269 } else if (sscanf(inbuf, fmt33, s1, s2, s3, s4, s5, s6) == 6) {
270 copynchars(tt->plrole, s1, (int) (sizeof tt->plrole) - 1);
271 copynchars(tt->plrace, s2, (int) (sizeof tt->plrace) - 1);
272 copynchars(tt->plgend, s3, (int) (sizeof tt->plgend) - 1);
273 copynchars(tt->plalign, s4, (int) (sizeof tt->plalign) - 1);
274 copynchars(tt->name, s5, (int) (sizeof tt->name) - 1);
275 copynchars(tt->death, s6, (int) (sizeof tt->death) - 1);
276 } else
277 tt->points = 0;
278 #ifdef NO_SCAN_BRACK
279 if (tt->points > 0) {
280 nsb_unmung_line(tt->name);
281 nsb_unmung_line(tt->death);
283 #endif
286 /* check old score entries for Y2K problem and fix whenever found */
287 if (tt->points > 0) {
288 if (tt->birthdate < 19000000L)
289 tt->birthdate += 19000000L;
290 if (tt->deathdate < 19000000L)
291 tt->deathdate += 19000000L;
295 STATIC_OVL void
296 writeentry(rfile, tt)
297 FILE *rfile;
298 struct toptenentry *tt;
300 static const char fmt32[] = "%c%c "; /* role,gender */
301 static const char fmt33[] = "%s %s %s %s "; /* role,race,gndr,algn */
302 #ifndef NO_SCAN_BRACK
303 static const char fmt0[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
304 static const char fmtX[] = "%s,%s\n";
305 #else /* NO_SCAN_BRACK */
306 static const char fmt0[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d ";
307 static const char fmtX[] = "%s %s\n";
309 nsb_mung_line(tt->name);
310 nsb_mung_line(tt->death);
311 #endif
313 (void) fprintf(rfile, fmt0, tt->ver_major, tt->ver_minor, tt->patchlevel,
314 tt->points, tt->deathdnum, tt->deathlev, tt->maxlvl,
315 tt->hp, tt->maxhp, tt->deaths, tt->deathdate,
316 tt->birthdate, tt->uid);
317 if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3))
318 (void) fprintf(rfile, fmt32, tt->plrole[0], tt->plgend[0]);
319 else
320 (void) fprintf(rfile, fmt33, tt->plrole, tt->plrace, tt->plgend,
321 tt->plalign);
322 (void) fprintf(rfile, fmtX, onlyspace(tt->name) ? "_" : tt->name,
323 tt->death);
325 #ifdef NO_SCAN_BRACK
326 nsb_unmung_line(tt->name);
327 nsb_unmung_line(tt->death);
328 #endif
331 #ifdef XLOGFILE
333 /* as tab is never used in eg. plname or death, no need to mangle those. */
334 STATIC_OVL void
335 writexlentry(rfile, tt, how)
336 FILE *rfile;
337 struct toptenentry *tt;
338 int how;
340 #define Fprintf (void) fprintf
341 #define XLOG_SEP '\t' /* xlogfile field separator. */
342 char buf[BUFSZ], tmpbuf[DTHSZ + 1];
344 Sprintf(buf, "version=%d.%d.%d", tt->ver_major, tt->ver_minor,
345 tt->patchlevel);
346 Sprintf(eos(buf), "%cpoints=%ld%cdeathdnum=%d%cdeathlev=%d", XLOG_SEP,
347 tt->points, XLOG_SEP, tt->deathdnum, XLOG_SEP, tt->deathlev);
348 Sprintf(eos(buf), "%cmaxlvl=%d%chp=%d%cmaxhp=%d", XLOG_SEP, tt->maxlvl,
349 XLOG_SEP, tt->hp, XLOG_SEP, tt->maxhp);
350 Sprintf(eos(buf), "%cdeaths=%d%cdeathdate=%ld%cbirthdate=%ld%cuid=%d",
351 XLOG_SEP, tt->deaths, XLOG_SEP, tt->deathdate, XLOG_SEP,
352 tt->birthdate, XLOG_SEP, tt->uid);
353 Fprintf(rfile, "%s", buf);
354 Sprintf(buf, "%crole=%s%crace=%s%cgender=%s%calign=%s", XLOG_SEP,
355 tt->plrole, XLOG_SEP, tt->plrace, XLOG_SEP, tt->plgend, XLOG_SEP,
356 tt->plalign);
357 /* make a copy of death reason that doesn't include ", while helpless" */
358 formatkiller(tmpbuf, sizeof tmpbuf, how, FALSE);
359 Fprintf(rfile, "%s%cname=%s%cdeath=%s",
360 buf, /* (already includes separator) */
361 XLOG_SEP, plname, XLOG_SEP, tmpbuf);
362 if (multi)
363 Fprintf(rfile, "%cwhile=%s", XLOG_SEP,
364 multi_reason ? multi_reason : "helpless");
365 Fprintf(rfile, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP,
366 encodeconduct(), XLOG_SEP, moves, XLOG_SEP, encodeachieve());
367 Fprintf(rfile, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP,
368 (long) urealtime.realtime, XLOG_SEP,
369 (long) ubirthday, XLOG_SEP, (long) urealtime.finish_time);
370 Fprintf(rfile, "%cgender0=%s%calign0=%s", XLOG_SEP,
371 genders[flags.initgend].filecode, XLOG_SEP,
372 aligns[1 - u.ualignbase[A_ORIGINAL]].filecode);
373 Fprintf(rfile, "%cflags=0x%lx", XLOG_SEP, encodexlogflags());
374 Fprintf(rfile, "\n");
375 #undef XLOG_SEP
378 STATIC_OVL long
379 encodexlogflags()
381 long e = 0L;
383 if (wizard)
384 e |= 1L << 0;
385 if (discover)
386 e |= 1L << 1;
387 if (!u.uroleplay.numbones)
388 e |= 1L << 2;
390 return e;
393 STATIC_OVL long
394 encodeconduct()
396 long e = 0L;
398 if (!u.uconduct.food)
399 e |= 1L << 0;
400 if (!u.uconduct.unvegan)
401 e |= 1L << 1;
402 if (!u.uconduct.unvegetarian)
403 e |= 1L << 2;
404 if (!u.uconduct.gnostic)
405 e |= 1L << 3;
406 if (!u.uconduct.weaphit)
407 e |= 1L << 4;
408 if (!u.uconduct.killer)
409 e |= 1L << 5;
410 if (!u.uconduct.literate)
411 e |= 1L << 6;
412 if (!u.uconduct.polypiles)
413 e |= 1L << 7;
414 if (!u.uconduct.polyselfs)
415 e |= 1L << 8;
416 if (!u.uconduct.wishes)
417 e |= 1L << 9;
418 if (!u.uconduct.wisharti)
419 e |= 1L << 10;
420 if (!num_genocides())
421 e |= 1L << 11;
423 return e;
426 STATIC_OVL long
427 encodeachieve()
429 long r = 0L;
431 if (u.uachieve.bell)
432 r |= 1L << 0;
433 if (u.uachieve.enter_gehennom)
434 r |= 1L << 1;
435 if (u.uachieve.menorah)
436 r |= 1L << 2;
437 if (u.uachieve.book)
438 r |= 1L << 3;
439 if (u.uevent.invoked)
440 r |= 1L << 4;
441 if (u.uachieve.amulet)
442 r |= 1L << 5;
443 if (In_endgame(&u.uz))
444 r |= 1L << 6;
445 if (Is_astralevel(&u.uz))
446 r |= 1L << 7;
447 if (u.uachieve.ascended)
448 r |= 1L << 8;
449 if (u.uachieve.mines_luckstone)
450 r |= 1L << 9;
451 if (u.uachieve.finish_sokoban)
452 r |= 1L << 10;
453 if (u.uachieve.killed_medusa)
454 r |= 1L << 11;
455 if (u.uroleplay.blind)
456 r |= 1L << 12;
457 if (u.uroleplay.nudist)
458 r |= 1L << 13;
460 return r;
463 #endif /* XLOGFILE */
465 STATIC_OVL void
466 free_ttlist(tt)
467 struct toptenentry *tt;
469 struct toptenentry *ttnext;
471 while (tt->points > 0) {
472 ttnext = tt->tt_next;
473 dealloc_ttentry(tt);
474 tt = ttnext;
476 dealloc_ttentry(tt);
479 void
480 topten(how, when)
481 int how;
482 time_t when;
484 int uid = getuid();
485 int rank, rank0 = -1, rank1 = 0;
486 int occ_cnt = sysopt.persmax;
487 register struct toptenentry *t0, *tprev;
488 struct toptenentry *t1;
489 FILE *rfile;
490 register int flg = 0;
491 boolean t0_used;
492 #ifdef LOGFILE
493 FILE *lfile;
494 #endif /* LOGFILE */
495 #ifdef XLOGFILE
496 FILE *xlfile;
497 #endif /* XLOGFILE */
499 #ifdef _DCC
500 /* Under DICE 3.0, this crashes the system consistently, apparently due to
501 * corruption of *rfile somewhere. Until I figure this out, just cut out
502 * topten support entirely - at least then the game exits cleanly. --AC
504 return;
505 #endif
507 /* If we are in the midst of a panic, cut out topten entirely.
508 * topten uses alloc() several times, which will lead to
509 * problems if the panic was the result of an alloc() failure.
511 if (program_state.panicking)
512 return;
514 if (iflags.toptenwin) {
515 toptenwin = create_nhwindow(NHW_TEXT);
518 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
519 #define HUP if (!program_state.done_hup)
520 #else
521 #define HUP
522 #endif
524 #ifdef TOS
525 restore_colors(); /* make sure the screen is black on white */
526 #endif
527 /* create a new 'topten' entry */
528 t0_used = FALSE;
529 t0 = newttentry();
530 t0->ver_major = VERSION_MAJOR;
531 t0->ver_minor = VERSION_MINOR;
532 t0->patchlevel = PATCHLEVEL;
533 t0->points = u.urexp;
534 t0->deathdnum = u.uz.dnum;
535 /* deepest_lev_reached() is in terms of depth(), and reporting the
536 * deepest level reached in the dungeon death occurred in doesn't
537 * seem right, so we have to report the death level in depth() terms
538 * as well (which also seems reasonable since that's all the player
539 * sees on the screen anyway)
541 t0->deathlev = observable_depth(&u.uz);
542 t0->maxlvl = deepest_lev_reached(TRUE);
543 t0->hp = u.uhp;
544 t0->maxhp = u.uhpmax;
545 t0->deaths = u.umortality;
546 t0->uid = uid;
547 copynchars(t0->plrole, urole.filecode, ROLESZ);
548 copynchars(t0->plrace, urace.filecode, ROLESZ);
549 copynchars(t0->plgend, genders[flags.female].filecode, ROLESZ);
550 copynchars(t0->plalign, aligns[1 - u.ualign.type].filecode, ROLESZ);
551 copynchars(t0->name, plname, NAMSZ);
552 formatkiller(t0->death, sizeof t0->death, how, TRUE);
553 t0->birthdate = yyyymmdd(ubirthday);
554 t0->deathdate = yyyymmdd(when);
555 t0->tt_next = 0;
556 #ifdef UPDATE_RECORD_IN_PLACE
557 t0->fpos = -1L;
558 #endif
560 #ifdef LOGFILE /* used for debugging (who dies of what, where) */
561 if (lock_file(LOGFILE, SCOREPREFIX, 10)) {
562 if (!(lfile = fopen_datafile(LOGFILE, "a", SCOREPREFIX))) {
563 HUP raw_print("Cannot open log file!");
564 } else {
565 writeentry(lfile, t0);
566 (void) fclose(lfile);
568 unlock_file(LOGFILE);
570 #endif /* LOGFILE */
571 #ifdef XLOGFILE
572 if (lock_file(XLOGFILE, SCOREPREFIX, 10)) {
573 if (!(xlfile = fopen_datafile(XLOGFILE, "a", SCOREPREFIX))) {
574 HUP raw_print("Cannot open extended log file!");
575 } else {
576 writexlentry(xlfile, t0, how);
577 (void) fclose(xlfile);
579 unlock_file(XLOGFILE);
581 #endif /* XLOGFILE */
583 if (wizard || discover) {
584 if (how != PANICKED)
585 HUP {
586 char pbuf[BUFSZ];
588 topten_print("");
589 Sprintf(pbuf,
590 "Since you were in %s mode, the score list will not be checked.",
591 wizard ? "wizard" : "discover");
592 topten_print(pbuf);
594 goto showwin;
597 if (!lock_file(RECORD, SCOREPREFIX, 60))
598 goto destroywin;
600 #ifdef UPDATE_RECORD_IN_PLACE
601 rfile = fopen_datafile(RECORD, "r+", SCOREPREFIX);
602 #else
603 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
604 #endif
606 if (!rfile) {
607 HUP raw_print("Cannot open record file!");
608 unlock_file(RECORD);
609 goto destroywin;
612 HUP topten_print("");
614 /* assure minimum number of points */
615 if (t0->points < sysopt.pointsmin)
616 t0->points = 0;
618 t1 = tt_head = newttentry();
619 tprev = 0;
620 /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
621 for (rank = 1;;) {
622 readentry(rfile, t1);
623 if (t1->points < sysopt.pointsmin)
624 t1->points = 0;
625 if (rank0 < 0 && t1->points < t0->points) {
626 rank0 = rank++;
627 if (tprev == 0)
628 tt_head = t0;
629 else
630 tprev->tt_next = t0;
631 t0->tt_next = t1;
632 #ifdef UPDATE_RECORD_IN_PLACE
633 t0->fpos = t1->fpos; /* insert here */
634 #endif
635 t0_used = TRUE;
636 occ_cnt--;
637 flg++; /* ask for a rewrite */
638 } else
639 tprev = t1;
641 if (t1->points == 0)
642 break;
643 if ((sysopt.pers_is_uid ? t1->uid == t0->uid
644 : strncmp(t1->name, t0->name, NAMSZ) == 0)
645 && !strncmp(t1->plrole, t0->plrole, ROLESZ) && --occ_cnt <= 0) {
646 if (rank0 < 0) {
647 rank0 = 0;
648 rank1 = rank;
649 HUP {
650 char pbuf[BUFSZ];
652 Sprintf(pbuf,
653 "You didn't beat your previous score of %ld points.",
654 t1->points);
655 topten_print(pbuf);
656 topten_print("");
659 if (occ_cnt < 0) {
660 flg++;
661 continue;
664 if (rank <= sysopt.entrymax) {
665 t1->tt_next = newttentry();
666 t1 = t1->tt_next;
667 rank++;
669 if (rank > sysopt.entrymax) {
670 t1->points = 0;
671 break;
674 if (flg) { /* rewrite record file */
675 #ifdef UPDATE_RECORD_IN_PLACE
676 (void) fseek(rfile, (t0->fpos >= 0 ? t0->fpos : final_fpos),
677 SEEK_SET);
678 #else
679 (void) fclose(rfile);
680 if (!(rfile = fopen_datafile(RECORD, "w", SCOREPREFIX))) {
681 HUP raw_print("Cannot write record file");
682 unlock_file(RECORD);
683 free_ttlist(tt_head);
684 goto destroywin;
686 #endif /* UPDATE_RECORD_IN_PLACE */
687 if (!done_stopprint)
688 if (rank0 > 0) {
689 if (rank0 <= 10) {
690 topten_print("You made the top ten list!");
691 } else {
692 char pbuf[BUFSZ];
694 Sprintf(pbuf,
695 "You reached the %d%s place on the top %d list.",
696 rank0, ordin(rank0), sysopt.entrymax);
697 topten_print(pbuf);
699 topten_print("");
702 if (rank0 == 0)
703 rank0 = rank1;
704 if (rank0 <= 0)
705 rank0 = rank;
706 if (!done_stopprint)
707 outheader();
708 t1 = tt_head;
709 for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
710 if (flg
711 #ifdef UPDATE_RECORD_IN_PLACE
712 && rank >= rank0
713 #endif
715 writeentry(rfile, t1);
716 if (done_stopprint)
717 continue;
718 if (rank > flags.end_top && (rank < rank0 - flags.end_around
719 || rank > rank0 + flags.end_around)
720 && (!flags.end_own
721 || (sysopt.pers_is_uid
722 ? t1->uid == t0->uid
723 : strncmp(t1->name, t0->name, NAMSZ) == 0)))
724 continue;
725 if (rank == rank0 - flags.end_around
726 && rank0 > flags.end_top + flags.end_around + 1 && !flags.end_own)
727 topten_print("");
728 if (rank != rank0)
729 outentry(rank, t1, FALSE);
730 else if (!rank1)
731 outentry(rank, t1, TRUE);
732 else {
733 outentry(rank, t1, TRUE);
734 outentry(0, t0, TRUE);
737 if (rank0 >= rank)
738 if (!done_stopprint)
739 outentry(0, t0, TRUE);
740 #ifdef UPDATE_RECORD_IN_PLACE
741 if (flg) {
742 #ifdef TRUNCATE_FILE
743 /* if a reasonable way to truncate a file exists, use it */
744 truncate_file(rfile);
745 #else
746 /* use sentinel record rather than relying on truncation */
747 t1->points = 0L; /* terminates file when read back in */
748 t1->ver_major = t1->ver_minor = t1->patchlevel = 0;
749 t1->uid = t1->deathdnum = t1->deathlev = 0;
750 t1->maxlvl = t1->hp = t1->maxhp = t1->deaths = 0;
751 t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-';
752 t1->plrole[1] = t1->plrace[1] = t1->plgend[1] = t1->plalign[1] = 0;
753 t1->birthdate = t1->deathdate = yyyymmdd((time_t) 0L);
754 Strcpy(t1->name, "@");
755 Strcpy(t1->death, "<eod>\n");
756 writeentry(rfile, t1);
757 (void) fflush(rfile);
758 #endif /* TRUNCATE_FILE */
760 #endif /* UPDATE_RECORD_IN_PLACE */
761 (void) fclose(rfile);
762 unlock_file(RECORD);
763 free_ttlist(tt_head);
765 showwin:
766 if (iflags.toptenwin && !done_stopprint)
767 display_nhwindow(toptenwin, 1);
768 destroywin:
769 if (!t0_used)
770 dealloc_ttentry(t0);
771 if (iflags.toptenwin) {
772 destroy_nhwindow(toptenwin);
773 toptenwin = WIN_ERR;
777 STATIC_OVL void
778 outheader()
780 char linebuf[BUFSZ];
781 register char *bp;
783 Strcpy(linebuf, " No Points Name");
784 bp = eos(linebuf);
785 while (bp < linebuf + COLNO - 9)
786 *bp++ = ' ';
787 Strcpy(bp, "Hp [max]");
788 topten_print(linebuf);
791 /* so>0: standout line; so=0: ordinary line */
792 STATIC_OVL void
793 outentry(rank, t1, so)
794 struct toptenentry *t1;
795 int rank;
796 boolean so;
798 boolean second_line = TRUE;
799 char linebuf[BUFSZ];
800 char *bp, hpbuf[24], linebuf3[BUFSZ];
801 int hppos, lngr;
803 linebuf[0] = '\0';
804 if (rank)
805 Sprintf(eos(linebuf), "%3d", rank);
806 else
807 Strcat(linebuf, " ");
809 Sprintf(eos(linebuf), " %10ld %.10s", t1->points ? t1->points : u.urexp,
810 t1->name);
811 Sprintf(eos(linebuf), "-%s", t1->plrole);
812 if (t1->plrace[0] != '?')
813 Sprintf(eos(linebuf), "-%s", t1->plrace);
814 /* Printing of gender and alignment is intentional. It has been
815 * part of the NetHack Geek Code, and illustrates a proper way to
816 * specify a character from the command line.
818 Sprintf(eos(linebuf), "-%s", t1->plgend);
819 if (t1->plalign[0] != '?')
820 Sprintf(eos(linebuf), "-%s ", t1->plalign);
821 else
822 Strcat(linebuf, " ");
823 if (!strncmp("escaped", t1->death, 7)) {
824 Sprintf(eos(linebuf), "escaped the dungeon %s[max level %d]",
825 !strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "",
826 t1->maxlvl);
827 /* fixup for closing paren in "escaped... with...Amulet)[max..." */
828 if ((bp = index(linebuf, ')')) != 0)
829 *bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' ';
830 second_line = FALSE;
831 } else if (!strncmp("ascended", t1->death, 8)) {
832 Sprintf(eos(linebuf), "ascended to demigod%s-hood",
833 (t1->plgend[0] == 'F') ? "dess" : "");
834 second_line = FALSE;
835 } else {
836 if (!strncmp(t1->death, "quit", 4)) {
837 Strcat(linebuf, "quit");
838 second_line = FALSE;
839 } else if (!strncmp(t1->death, "died of st", 10)) {
840 Strcat(linebuf, "starved to death");
841 second_line = FALSE;
842 } else if (!strncmp(t1->death, "choked", 6)) {
843 Sprintf(eos(linebuf), "choked on h%s food",
844 (t1->plgend[0] == 'F') ? "er" : "is");
845 } else if (!strncmp(t1->death, "poisoned", 8)) {
846 Strcat(linebuf, "was poisoned");
847 } else if (!strncmp(t1->death, "crushed", 7)) {
848 Strcat(linebuf, "was crushed to death");
849 } else if (!strncmp(t1->death, "petrified by ", 13)) {
850 Strcat(linebuf, "turned to stone");
851 } else
852 Strcat(linebuf, "died");
854 if (t1->deathdnum == astral_level.dnum) {
855 const char *arg, *fmt = " on the Plane of %s";
857 switch (t1->deathlev) {
858 case -5:
859 fmt = " on the %s Plane";
860 arg = "Astral";
861 break;
862 case -4:
863 arg = "Water";
864 break;
865 case -3:
866 arg = "Fire";
867 break;
868 case -2:
869 arg = "Air";
870 break;
871 case -1:
872 arg = "Earth";
873 break;
874 default:
875 arg = "Void";
876 break;
878 Sprintf(eos(linebuf), fmt, arg);
879 } else {
880 Sprintf(eos(linebuf), " in %s", dungeons[t1->deathdnum].dname);
881 if (t1->deathdnum != knox_level.dnum)
882 Sprintf(eos(linebuf), " on level %d", t1->deathlev);
883 if (t1->deathlev != t1->maxlvl)
884 Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
887 /* kludge for "quit while already on Charon's boat" */
888 if (!strncmp(t1->death, "quit ", 5))
889 Strcat(linebuf, t1->death + 4);
891 Strcat(linebuf, ".");
893 /* Quit, starved, ascended, and escaped contain no second line */
894 if (second_line)
895 Sprintf(eos(linebuf), " %c%s.", highc(*(t1->death)), t1->death + 1);
897 lngr = (int) strlen(linebuf);
898 if (t1->hp <= 0)
899 hpbuf[0] = '-', hpbuf[1] = '\0';
900 else
901 Sprintf(hpbuf, "%d", t1->hp);
902 /* beginning of hp column after padding (not actually padded yet) */
903 hppos = COLNO - (sizeof(" Hp [max]") - 1); /* sizeof(str) includes \0 */
904 while (lngr >= hppos) {
905 for (bp = eos(linebuf); !(*bp == ' ' && (bp - linebuf < hppos)); bp--)
907 /* special case: word is too long, wrap in the middle */
908 if (linebuf + 15 >= bp)
909 bp = linebuf + hppos - 1;
910 /* special case: if about to wrap in the middle of maximum
911 dungeon depth reached, wrap in front of it instead */
912 if (bp > linebuf + 5 && !strncmp(bp - 5, " [max", 5))
913 bp -= 5;
914 if (*bp != ' ')
915 Strcpy(linebuf3, bp);
916 else
917 Strcpy(linebuf3, bp + 1);
918 *bp = 0;
919 if (so) {
920 while (bp < linebuf + (COLNO - 1))
921 *bp++ = ' ';
922 *bp = 0;
923 topten_print_bold(linebuf);
924 } else
925 topten_print(linebuf);
926 Sprintf(linebuf, "%15s %s", "", linebuf3);
927 lngr = strlen(linebuf);
929 /* beginning of hp column not including padding */
930 hppos = COLNO - 7 - (int) strlen(hpbuf);
931 bp = eos(linebuf);
933 if (bp <= linebuf + hppos) {
934 /* pad any necessary blanks to the hit point entry */
935 while (bp < linebuf + hppos)
936 *bp++ = ' ';
937 Strcpy(bp, hpbuf);
938 Sprintf(eos(bp), " %s[%d]",
939 (t1->maxhp < 10) ? " " : (t1->maxhp < 100) ? " " : "",
940 t1->maxhp);
943 if (so) {
944 bp = eos(linebuf);
945 if (so >= COLNO)
946 so = COLNO - 1;
947 while (bp < linebuf + so)
948 *bp++ = ' ';
949 *bp = 0;
950 topten_print_bold(linebuf);
951 } else
952 topten_print(linebuf);
955 STATIC_OVL int
956 score_wanted(current_ver, rank, t1, playerct, players, uid)
957 boolean current_ver;
958 int rank;
959 struct toptenentry *t1;
960 int playerct;
961 const char **players;
962 int uid;
964 int i;
966 if (current_ver
967 && (t1->ver_major != VERSION_MAJOR || t1->ver_minor != VERSION_MINOR
968 || t1->patchlevel != PATCHLEVEL))
969 return 0;
971 if (sysopt.pers_is_uid && !playerct && t1->uid == uid)
972 return 1;
974 for (i = 0; i < playerct; i++) {
975 if (players[i][0] == '-' && index("pr", players[i][1])
976 && players[i][2] == 0 && i + 1 < playerct) {
977 const char *arg = players[i + 1];
978 if ((players[i][1] == 'p'
979 && str2role(arg) == str2role(t1->plrole))
980 || (players[i][1] == 'r'
981 && str2race(arg) == str2race(t1->plrace)))
982 return 1;
983 i++;
984 } else if (strcmp(players[i], "all") == 0
985 || strncmp(t1->name, players[i], NAMSZ) == 0
986 || (players[i][0] == '-' && players[i][1] == t1->plrole[0]
987 && players[i][2] == 0)
988 || (digit(players[i][0]) && rank <= atoi(players[i])))
989 return 1;
991 return 0;
995 * print selected parts of score list.
996 * argc >= 2, with argv[0] untrustworthy (directory names, et al.),
997 * and argv[1] starting with "-s".
999 void
1000 prscore(argc, argv)
1001 int argc;
1002 char **argv;
1004 const char **players;
1005 int playerct, rank;
1006 boolean current_ver = TRUE, init_done = FALSE;
1007 register struct toptenentry *t1;
1008 FILE *rfile;
1009 boolean match_found = FALSE;
1010 register int i;
1011 char pbuf[BUFSZ];
1012 int uid = -1;
1013 const char *player0;
1015 if (argc < 2 || strncmp(argv[1], "-s", 2)) {
1016 raw_printf("prscore: bad arguments (%d)", argc);
1017 return;
1020 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
1021 if (!rfile) {
1022 raw_print("Cannot open record file!");
1023 return;
1026 #ifdef AMIGA
1028 extern winid amii_rawprwin;
1030 init_nhwindows(&argc, argv);
1031 amii_rawprwin = create_nhwindow(NHW_TEXT);
1033 #endif
1035 /* If the score list isn't after a game, we never went through
1036 * initialization. */
1037 if (wiz1_level.dlevel == 0) {
1038 dlb_init();
1039 init_dungeons();
1040 init_done = TRUE;
1043 if (!argv[1][2]) { /* plain "-s" */
1044 argc--;
1045 argv++;
1046 } else
1047 argv[1] += 2;
1049 if (argc > 1 && !strcmp(argv[1], "-v")) {
1050 current_ver = FALSE;
1051 argc--;
1052 argv++;
1055 if (argc <= 1) {
1056 if (sysopt.pers_is_uid) {
1057 uid = getuid();
1058 playerct = 0;
1059 players = (const char **) 0;
1060 } else {
1061 player0 = plname;
1062 if (!*player0)
1063 #ifdef AMIGA
1064 player0 = "all"; /* single user system */
1065 #else
1066 player0 = "hackplayer";
1067 #endif
1068 playerct = 1;
1069 players = &player0;
1071 } else {
1072 playerct = --argc;
1073 players = (const char **) ++argv;
1075 raw_print("");
1077 t1 = tt_head = newttentry();
1078 for (rank = 1;; rank++) {
1079 readentry(rfile, t1);
1080 if (t1->points == 0)
1081 break;
1082 if (!match_found
1083 && score_wanted(current_ver, rank, t1, playerct, players, uid))
1084 match_found = TRUE;
1085 t1->tt_next = newttentry();
1086 t1 = t1->tt_next;
1089 (void) fclose(rfile);
1090 if (init_done) {
1091 free_dungeons();
1092 dlb_cleanup();
1095 if (match_found) {
1096 outheader();
1097 t1 = tt_head;
1098 for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
1099 if (score_wanted(current_ver, rank, t1, playerct, players, uid))
1100 (void) outentry(rank, t1, FALSE);
1102 } else {
1103 Sprintf(pbuf, "Cannot find any %sentries for ",
1104 current_ver ? "current " : "");
1105 if (playerct < 1)
1106 Strcat(pbuf, "you.");
1107 else {
1108 if (playerct > 1)
1109 Strcat(pbuf, "any of ");
1110 for (i = 0; i < playerct; i++) {
1111 /* stop printing players if there are too many to fit */
1112 if (strlen(pbuf) + strlen(players[i]) + 2 >= BUFSZ) {
1113 if (strlen(pbuf) < BUFSZ - 4)
1114 Strcat(pbuf, "...");
1115 else
1116 Strcpy(pbuf + strlen(pbuf) - 4, "...");
1117 break;
1119 Strcat(pbuf, players[i]);
1120 if (i < playerct - 1) {
1121 if (players[i][0] == '-' && index("pr", players[i][1])
1122 && players[i][2] == 0)
1123 Strcat(pbuf, " ");
1124 else
1125 Strcat(pbuf, ":");
1129 raw_print(pbuf);
1130 raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
1132 hname);
1133 raw_printf("Player types are: [-p role] [-r race]");
1135 free_ttlist(tt_head);
1136 #ifdef AMIGA
1138 extern winid amii_rawprwin;
1140 display_nhwindow(amii_rawprwin, 1);
1141 destroy_nhwindow(amii_rawprwin);
1142 amii_rawprwin = WIN_ERR;
1144 #endif
1147 STATIC_OVL int
1148 classmon(plch, fem)
1149 char *plch;
1150 boolean fem;
1152 int i;
1154 /* Look for this role in the role table */
1155 for (i = 0; roles[i].name.m; i++)
1156 if (!strncmp(plch, roles[i].filecode, ROLESZ)) {
1157 if (fem && roles[i].femalenum != NON_PM)
1158 return roles[i].femalenum;
1159 else if (roles[i].malenum != NON_PM)
1160 return roles[i].malenum;
1161 else
1162 return PM_HUMAN;
1164 /* this might be from a 3.2.x score for former Elf class */
1165 if (!strcmp(plch, "E"))
1166 return PM_RANGER;
1168 impossible("What weird role is this? (%s)", plch);
1169 return PM_HUMAN_MUMMY;
1173 * Get a random player name and class from the high score list,
1175 struct toptenentry *
1176 get_rnd_toptenentry()
1178 int rank, i;
1179 FILE *rfile;
1180 register struct toptenentry *tt;
1181 static struct toptenentry tt_buf;
1183 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
1184 if (!rfile) {
1185 impossible("Cannot open record file!");
1186 return NULL;
1189 tt = &tt_buf;
1190 rank = rnd(sysopt.tt_oname_maxrank);
1191 pickentry:
1192 for (i = rank; i; i--) {
1193 readentry(rfile, tt);
1194 if (tt->points == 0)
1195 break;
1198 if (tt->points == 0) {
1199 if (rank > 1) {
1200 rank = 1;
1201 rewind(rfile);
1202 goto pickentry;
1204 tt = NULL;
1207 (void) fclose(rfile);
1208 return tt;
1213 * Attach random player name and class from high score list
1214 * to an object (for statues or morgue corpses).
1216 struct obj *
1217 tt_oname(otmp)
1218 struct obj *otmp;
1220 struct toptenentry *tt;
1221 if (!otmp)
1222 return (struct obj *) 0;
1224 tt = get_rnd_toptenentry();
1226 if (!tt)
1227 return (struct obj *) 0;
1229 set_corpsenm(otmp, classmon(tt->plrole, (tt->plgend[0] == 'F')));
1230 otmp = oname(otmp, tt->name);
1232 return otmp;
1235 #ifdef NO_SCAN_BRACK
1236 /* Lattice scanf isn't up to reading the scorefile. What */
1237 /* follows deals with that; I admit it's ugly. (KL) */
1238 /* Now generally available (KL) */
1239 STATIC_OVL void
1240 nsb_mung_line(p)
1241 char *p;
1243 while ((p = index(p, ' ')) != 0)
1244 *p = '|';
1247 STATIC_OVL void
1248 nsb_unmung_line(p)
1249 char *p;
1251 while ((p = index(p, '|')) != 0)
1252 *p = ' ';
1254 #endif /* NO_SCAN_BRACK */
1256 /*topten.c*/