NHDT->ANH, nethack->anethack, nhdat->anhdat
[aNetHack.git] / src / topten.c
blobb9e1a9606622f535fd3edb989ae67a3d86be1585
1 /* aNetHack 0.0.1 topten.c $ANH-Date: 1450451497 2015/12/18 15:11:37 $ $ANH-Branch: master $:$ANH-Revision: 1.44 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* aNetHack 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 #ifndef NAMSZ
34 /* Changing NAMSZ can break your existing record/logfile */
35 #define NAMSZ 10
36 #endif
37 #define DTHSZ 100
38 #define ROLESZ 3
40 struct toptenentry {
41 struct toptenentry *tt_next;
42 #ifdef UPDATE_RECORD_IN_PLACE
43 long fpos;
44 #endif
45 long points;
46 int deathdnum, deathlev;
47 int maxlvl, hp, maxhp, deaths;
48 int ver_major, ver_minor, patchlevel;
49 long deathdate, birthdate;
50 int uid;
51 char plrole[ROLESZ + 1];
52 char plrace[ROLESZ + 1];
53 char plgend[ROLESZ + 1];
54 char plalign[ROLESZ + 1];
55 char name[NAMSZ + 1];
56 char death[DTHSZ + 1];
57 } * tt_head;
58 /* size big enough to read in all the string fields at once; includes
59 room for separating space or trailing newline plus string terminator */
60 #define SCANBUFSZ (4 * (ROLESZ + 1) + (NAMSZ + 1) + (DTHSZ + 1) + 1)
62 STATIC_DCL void FDECL(topten_print, (const char *));
63 STATIC_DCL void FDECL(topten_print_bold, (const char *));
64 STATIC_DCL xchar FDECL(observable_depth, (d_level *));
65 STATIC_DCL void NDECL(outheader);
66 STATIC_DCL void FDECL(outentry, (int, struct toptenentry *, BOOLEAN_P));
67 STATIC_DCL void FDECL(discardexcess, (FILE *));
68 STATIC_DCL void FDECL(readentry, (FILE *, struct toptenentry *));
69 STATIC_DCL void FDECL(writeentry, (FILE *, struct toptenentry *));
70 #ifdef XLOGFILE
71 STATIC_DCL void FDECL(writexlentry, (FILE *, struct toptenentry *, int));
72 STATIC_DCL long NDECL(encodexlogflags);
73 STATIC_DCL long NDECL(encodeconduct);
74 STATIC_DCL long NDECL(encodeachieve);
75 #endif
76 STATIC_DCL void FDECL(free_ttlist, (struct toptenentry *));
77 STATIC_DCL int FDECL(classmon, (char *, BOOLEAN_P));
78 STATIC_DCL int FDECL(score_wanted, (BOOLEAN_P, int, struct toptenentry *, int,
79 const char **, int));
80 #ifdef NO_SCAN_BRACK
81 STATIC_DCL void FDECL(nsb_mung_line, (char *));
82 STATIC_DCL void FDECL(nsb_unmung_line, (char *));
83 #endif
85 static winid toptenwin = WIN_ERR;
87 /* "killed by",&c ["an"] 'killer.name' */
88 void
89 formatkiller(buf, siz, how, incl_helpless)
90 char *buf;
91 unsigned siz;
92 int how;
93 boolean incl_helpless;
95 static NEARDATA const char *const killed_by_prefix[] = {
96 /* DIED, CHOKING, POISONING, STARVING, */
97 "killed by ", "choked on ", "poisoned by ", "died of ",
98 /* DROWNING, BURNING, DISSOLVED, CRUSHING, */
99 "drowned in ", "burned by ", "dissolved in ", "crushed to death by ",
100 /* STONING, TURNED_SLIME, GENOCIDED, */
101 "petrified by ", "turned to slime by ", "killed by ",
102 /* PANICKED, TRICKED, QUIT, ESCAPED, ASCENDED */
103 "", "", "", "", ""
105 unsigned l;
106 char c, *kname = killer.name;
108 buf[0] = '\0'; /* lint suppression */
109 switch (killer.format) {
110 default:
111 impossible("bad killer format? (%d)", killer.format);
112 /*FALLTHRU*/
113 case NO_KILLER_PREFIX:
114 break;
115 case KILLED_BY_AN:
116 kname = an(kname);
117 /*FALLTHRU*/
118 case KILLED_BY:
119 (void) strncat(buf, killed_by_prefix[how], siz - 1);
120 l = strlen(buf);
121 buf += l, siz -= l;
122 break;
124 /* Copy kname into buf[].
125 * Object names and named fruit have already been sanitized, but
126 * monsters can have "called 'arbitrary text'" attached to them,
127 * so make sure that that text can't confuse field splitting when
128 * record, logfile, or xlogfile is re-read at some later point.
130 while (--siz > 0) {
131 c = *kname++;
132 if (c == ',')
133 c = ';';
134 /* 'xlogfile' doesn't really need protection for '=', but
135 fixrecord.awk for corrupted 3.6.0 'record' does (only
136 if using xlogfile rather than logfile to repair record) */
137 else if (c == '=')
138 c = '_';
139 /* tab is not possible due to use of mungspaces() when naming;
140 it would disrupt xlogfile parsing if it were present */
141 else if (c == '\t')
142 c = ' ';
143 *buf++ = c;
145 *buf = '\0';
147 if (incl_helpless && multi) {
148 /* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */
149 if (multi_reason && strlen(multi_reason) + sizeof ", while " <= siz)
150 Sprintf(buf, ", while %s", multi_reason);
151 /* either multi_reason wasn't specified or wouldn't fit */
152 else if (sizeof ", while helpless" <= siz)
153 Strcpy(buf, ", while helpless");
154 /* else extra death info won't fit, so leave it out */
158 STATIC_OVL void
159 topten_print(x)
160 const char *x;
162 if (toptenwin == WIN_ERR)
163 raw_print(x);
164 else
165 putstr(toptenwin, ATR_NONE, x);
168 STATIC_OVL void
169 topten_print_bold(x)
170 const char *x;
172 if (toptenwin == WIN_ERR)
173 raw_print_bold(x);
174 else
175 putstr(toptenwin, ATR_BOLD, x);
178 STATIC_OVL xchar
179 observable_depth(lev)
180 d_level *lev;
182 #if 0
183 /* if we ever randomize the order of the elemental planes, we
184 must use a constant external representation in the record file */
185 if (In_endgame(lev)) {
186 if (Is_astralevel(lev))
187 return -5;
188 else if (Is_waterlevel(lev))
189 return -4;
190 else if (Is_firelevel(lev))
191 return -3;
192 else if (Is_airlevel(lev))
193 return -2;
194 else if (Is_earthlevel(lev))
195 return -1;
196 else
197 return 0; /* ? */
198 } else
199 #endif
200 return depth(lev);
203 /* throw away characters until current record has been entirely consumed */
204 STATIC_OVL void
205 discardexcess(rfile)
206 FILE *rfile;
208 int c;
210 do {
211 c = fgetc(rfile);
212 } while (c != '\n' && c != EOF);
215 STATIC_OVL void
216 readentry(rfile, tt)
217 FILE *rfile;
218 struct toptenentry *tt;
220 char inbuf[SCANBUFSZ], s1[SCANBUFSZ], s2[SCANBUFSZ], s3[SCANBUFSZ],
221 s4[SCANBUFSZ], s5[SCANBUFSZ], s6[SCANBUFSZ];
223 #ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
224 static const char fmt[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c";
225 static const char fmt32[] = "%c%c %s %s%*c";
226 static const char fmt33[] = "%s %s %s %s %s %s%*c";
227 #else
228 static const char fmt[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
229 static const char fmt32[] = "%c%c %[^,],%[^\n]%*c";
230 static const char fmt33[] = "%s %s %s %s %[^,],%[^\n]%*c";
231 #endif
233 #ifdef UPDATE_RECORD_IN_PLACE
234 /* note: input below must read the record's terminating newline */
235 final_fpos = tt->fpos = ftell(rfile);
236 #endif
237 #define TTFIELDS 13
238 if (fscanf(rfile, fmt, &tt->ver_major, &tt->ver_minor, &tt->patchlevel,
239 &tt->points, &tt->deathdnum, &tt->deathlev, &tt->maxlvl,
240 &tt->hp, &tt->maxhp, &tt->deaths, &tt->deathdate,
241 &tt->birthdate, &tt->uid) != TTFIELDS) {
242 #undef TTFIELDS
243 tt->points = 0;
244 discardexcess(rfile);
245 } else {
246 /* load remainder of record into a local buffer;
247 this imposes an implicit length limit of SCANBUFSZ
248 on every string field extracted from the buffer */
249 if (!fgets(inbuf, sizeof inbuf, rfile)) {
250 /* sscanf will fail and tt->points will be set to 0 */
251 *inbuf = '\0';
252 } else if (!index(inbuf, '\n')) {
253 Strcpy(&inbuf[sizeof inbuf - 2], "\n");
254 discardexcess(rfile);
256 /* Check for backwards compatibility */
257 if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3)) {
258 int i;
260 if (sscanf(inbuf, fmt32, tt->plrole, tt->plgend, s1, s2) == 4) {
261 tt->plrole[1] = tt->plgend[1] = '\0'; /* read via %c */
262 copynchars(tt->name, s1, (int) (sizeof tt->name) - 1);
263 copynchars(tt->death, s2, (int) (sizeof tt->death) - 1);
264 } else
265 tt->points = 0;
266 tt->plrole[1] = '\0';
267 if ((i = str2role(tt->plrole)) >= 0)
268 Strcpy(tt->plrole, roles[i].filecode);
269 Strcpy(tt->plrace, "?");
270 Strcpy(tt->plgend, (tt->plgend[0] == 'M') ? "Mal" : "Fem");
271 Strcpy(tt->plalign, "?");
272 } else if (sscanf(inbuf, fmt33, s1, s2, s3, s4, s5, s6) == 6) {
273 copynchars(tt->plrole, s1, (int) (sizeof tt->plrole) - 1);
274 copynchars(tt->plrace, s2, (int) (sizeof tt->plrace) - 1);
275 copynchars(tt->plgend, s3, (int) (sizeof tt->plgend) - 1);
276 copynchars(tt->plalign, s4, (int) (sizeof tt->plalign) - 1);
277 copynchars(tt->name, s5, (int) (sizeof tt->name) - 1);
278 copynchars(tt->death, s6, (int) (sizeof tt->death) - 1);
279 } else
280 tt->points = 0;
281 #ifdef NO_SCAN_BRACK
282 if (tt->points > 0) {
283 nsb_unmung_line(tt->name);
284 nsb_unmung_line(tt->death);
286 #endif
289 /* check old score entries for Y2K problem and fix whenever found */
290 if (tt->points > 0) {
291 if (tt->birthdate < 19000000L)
292 tt->birthdate += 19000000L;
293 if (tt->deathdate < 19000000L)
294 tt->deathdate += 19000000L;
298 STATIC_OVL void
299 writeentry(rfile, tt)
300 FILE *rfile;
301 struct toptenentry *tt;
303 static const char fmt32[] = "%c%c "; /* role,gender */
304 static const char fmt33[] = "%s %s %s %s "; /* role,race,gndr,algn */
305 #ifndef 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";
308 #else /* NO_SCAN_BRACK */
309 static const char fmt0[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d ";
310 static const char fmtX[] = "%s %s\n";
312 nsb_mung_line(tt->name);
313 nsb_mung_line(tt->death);
314 #endif
316 (void) fprintf(rfile, fmt0, tt->ver_major, tt->ver_minor, tt->patchlevel,
317 tt->points, tt->deathdnum, tt->deathlev, tt->maxlvl,
318 tt->hp, tt->maxhp, tt->deaths, tt->deathdate,
319 tt->birthdate, tt->uid);
320 if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3))
321 (void) fprintf(rfile, fmt32, tt->plrole[0], tt->plgend[0]);
322 else
323 (void) fprintf(rfile, fmt33, tt->plrole, tt->plrace, tt->plgend,
324 tt->plalign);
325 (void) fprintf(rfile, fmtX, onlyspace(tt->name) ? "_" : tt->name,
326 tt->death);
328 #ifdef NO_SCAN_BRACK
329 nsb_unmung_line(tt->name);
330 nsb_unmung_line(tt->death);
331 #endif
334 #ifdef XLOGFILE
336 /* as tab is never used in eg. plname or death, no need to mangle those. */
337 STATIC_OVL void
338 writexlentry(rfile, tt, how)
339 FILE *rfile;
340 struct toptenentry *tt;
341 int how;
343 #define Fprintf (void) fprintf
344 #define XLOG_SEP '\t' /* xlogfile field separator. */
345 char buf[BUFSZ], tmpbuf[DTHSZ + 1];
347 Sprintf(buf, "version=%d.%d.%d", tt->ver_major, tt->ver_minor,
348 tt->patchlevel);
349 Sprintf(eos(buf), "%cpoints=%ld%cdeathdnum=%d%cdeathlev=%d", XLOG_SEP,
350 tt->points, XLOG_SEP, tt->deathdnum, XLOG_SEP, tt->deathlev);
351 Sprintf(eos(buf), "%cmaxlvl=%d%chp=%d%cmaxhp=%d", XLOG_SEP, tt->maxlvl,
352 XLOG_SEP, tt->hp, XLOG_SEP, tt->maxhp);
353 Sprintf(eos(buf), "%cdeaths=%d%cdeathdate=%ld%cbirthdate=%ld%cuid=%d",
354 XLOG_SEP, tt->deaths, XLOG_SEP, tt->deathdate, XLOG_SEP,
355 tt->birthdate, XLOG_SEP, tt->uid);
356 Fprintf(rfile, "%s", buf);
357 Sprintf(buf, "%crole=%s%crace=%s%cgender=%s%calign=%s", XLOG_SEP,
358 tt->plrole, XLOG_SEP, tt->plrace, XLOG_SEP, tt->plgend, XLOG_SEP,
359 tt->plalign);
360 /* make a copy of death reason that doesn't include ", while helpless" */
361 formatkiller(tmpbuf, sizeof tmpbuf, how, FALSE);
362 Fprintf(rfile, "%s%cname=%s%cdeath=%s",
363 buf, /* (already includes separator) */
364 XLOG_SEP, plname, XLOG_SEP, tmpbuf);
365 if (multi)
366 Fprintf(rfile, "%cwhile=%s", XLOG_SEP,
367 multi_reason ? multi_reason : "helpless");
368 Fprintf(rfile, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP,
369 encodeconduct(), XLOG_SEP, moves, XLOG_SEP, encodeachieve());
370 Fprintf(rfile, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP,
371 (long) urealtime.realtime, XLOG_SEP,
372 (long) ubirthday, XLOG_SEP, (long) urealtime.finish_time);
373 Fprintf(rfile, "%cgender0=%s%calign0=%s", XLOG_SEP,
374 genders[flags.initgend].filecode, XLOG_SEP,
375 aligns[1 - u.ualignbase[A_ORIGINAL]].filecode);
376 Fprintf(rfile, "%cflags=0x%lx", XLOG_SEP, encodexlogflags());
377 Fprintf(rfile, "\n");
378 #undef XLOG_SEP
381 STATIC_OVL long
382 encodexlogflags()
384 long e = 0L;
386 if (wizard)
387 e |= 1L << 0;
388 if (discover)
389 e |= 1L << 1;
390 if (!u.uroleplay.numbones)
391 e |= 1L << 2;
393 return e;
396 STATIC_OVL long
397 encodeconduct()
399 long e = 0L;
401 if (!u.uconduct.food)
402 e |= 1L << 0;
403 if (!u.uconduct.unvegan)
404 e |= 1L << 1;
405 if (!u.uconduct.unvegetarian)
406 e |= 1L << 2;
407 if (!u.uconduct.gnostic)
408 e |= 1L << 3;
409 if (!u.uconduct.weaphit)
410 e |= 1L << 4;
411 if (!u.uconduct.killer)
412 e |= 1L << 5;
413 if (!u.uconduct.literate)
414 e |= 1L << 6;
415 if (!u.uconduct.polypiles)
416 e |= 1L << 7;
417 if (!u.uconduct.polyselfs)
418 e |= 1L << 8;
419 if (!u.uconduct.wishes)
420 e |= 1L << 9;
421 if (!u.uconduct.wisharti)
422 e |= 1L << 10;
423 if (!num_genocides())
424 e |= 1L << 11;
426 return e;
429 STATIC_OVL long
430 encodeachieve()
432 long r = 0L;
434 if (u.uachieve.bell)
435 r |= 1L << 0;
436 if (u.uachieve.enter_gehennom)
437 r |= 1L << 1;
438 if (u.uachieve.menorah)
439 r |= 1L << 2;
440 if (u.uachieve.book)
441 r |= 1L << 3;
442 if (u.uevent.invoked)
443 r |= 1L << 4;
444 if (u.uachieve.amulet)
445 r |= 1L << 5;
446 if (In_endgame(&u.uz))
447 r |= 1L << 6;
448 if (Is_astralevel(&u.uz))
449 r |= 1L << 7;
450 if (u.uachieve.ascended)
451 r |= 1L << 8;
452 if (u.uachieve.mines_luckstone)
453 r |= 1L << 9;
454 if (u.uachieve.finish_sokoban)
455 r |= 1L << 10;
456 if (u.uachieve.killed_medusa)
457 r |= 1L << 11;
458 if (u.uroleplay.blind)
459 r |= 1L << 12;
460 if (u.uroleplay.nudist)
461 r |= 1L << 13;
463 return r;
466 #endif /* XLOGFILE */
468 STATIC_OVL void
469 free_ttlist(tt)
470 struct toptenentry *tt;
472 struct toptenentry *ttnext;
474 while (tt->points > 0) {
475 ttnext = tt->tt_next;
476 dealloc_ttentry(tt);
477 tt = ttnext;
479 dealloc_ttentry(tt);
482 void
483 topten(how, when)
484 int how;
485 time_t when;
487 int uid = getuid();
488 int rank, rank0 = -1, rank1 = 0;
489 int occ_cnt = sysopt.persmax;
490 register struct toptenentry *t0, *tprev;
491 struct toptenentry *t1;
492 FILE *rfile;
493 register int flg = 0;
494 boolean t0_used;
495 #ifdef LOGFILE
496 FILE *lfile;
497 #endif /* LOGFILE */
498 #ifdef XLOGFILE
499 FILE *xlfile;
500 #endif /* XLOGFILE */
502 #ifdef _DCC
503 /* Under DICE 3.0, this crashes the system consistently, apparently due to
504 * corruption of *rfile somewhere. Until I figure this out, just cut out
505 * topten support entirely - at least then the game exits cleanly. --AC
507 return;
508 #endif
510 /* If we are in the midst of a panic, cut out topten entirely.
511 * topten uses alloc() several times, which will lead to
512 * problems if the panic was the result of an alloc() failure.
514 if (program_state.panicking)
515 return;
517 if (iflags.toptenwin) {
518 toptenwin = create_nhwindow(NHW_TEXT);
521 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
522 #define HUP if (!program_state.done_hup)
523 #else
524 #define HUP
525 #endif
527 #ifdef TOS
528 restore_colors(); /* make sure the screen is black on white */
529 #endif
530 /* create a new 'topten' entry */
531 t0_used = FALSE;
532 t0 = newttentry();
533 t0->ver_major = VERSION_MAJOR;
534 t0->ver_minor = VERSION_MINOR;
535 t0->patchlevel = PATCHLEVEL;
536 t0->points = u.urexp;
537 t0->deathdnum = u.uz.dnum;
538 /* deepest_lev_reached() is in terms of depth(), and reporting the
539 * deepest level reached in the dungeon death occurred in doesn't
540 * seem right, so we have to report the death level in depth() terms
541 * as well (which also seems reasonable since that's all the player
542 * sees on the screen anyway)
544 t0->deathlev = observable_depth(&u.uz);
545 t0->maxlvl = deepest_lev_reached(TRUE);
546 t0->hp = u.uhp;
547 t0->maxhp = u.uhpmax;
548 t0->deaths = u.umortality;
549 t0->uid = uid;
550 copynchars(t0->plrole, urole.filecode, ROLESZ);
551 copynchars(t0->plrace, urace.filecode, ROLESZ);
552 copynchars(t0->plgend, genders[flags.female].filecode, ROLESZ);
553 copynchars(t0->plalign, aligns[1 - u.ualign.type].filecode, ROLESZ);
554 copynchars(t0->name, plname, NAMSZ);
555 formatkiller(t0->death, sizeof t0->death, how, TRUE);
556 t0->birthdate = yyyymmdd(ubirthday);
557 t0->deathdate = yyyymmdd(when);
558 t0->tt_next = 0;
559 #ifdef UPDATE_RECORD_IN_PLACE
560 t0->fpos = -1L;
561 #endif
563 #ifdef LOGFILE /* used for debugging (who dies of what, where) */
564 if (lock_file(LOGFILE, SCOREPREFIX, 10)) {
565 if (!(lfile = fopen_datafile(LOGFILE, "a", SCOREPREFIX))) {
566 HUP raw_print("Cannot open log file!");
567 } else {
568 writeentry(lfile, t0);
569 (void) fclose(lfile);
571 unlock_file(LOGFILE);
573 #endif /* LOGFILE */
574 #ifdef XLOGFILE
575 if (lock_file(XLOGFILE, SCOREPREFIX, 10)) {
576 if (!(xlfile = fopen_datafile(XLOGFILE, "a", SCOREPREFIX))) {
577 HUP raw_print("Cannot open extended log file!");
578 } else {
579 writexlentry(xlfile, t0, how);
580 (void) fclose(xlfile);
582 unlock_file(XLOGFILE);
584 #endif /* XLOGFILE */
586 if (wizard || discover) {
587 if (how != PANICKED)
588 HUP {
589 char pbuf[BUFSZ];
591 topten_print("");
592 Sprintf(pbuf,
593 "Since you were in %s mode, the score list will not be checked.",
594 wizard ? "wizard" : "discover");
595 topten_print(pbuf);
597 goto showwin;
600 if (!lock_file(RECORD, SCOREPREFIX, 60))
601 goto destroywin;
603 #ifdef UPDATE_RECORD_IN_PLACE
604 rfile = fopen_datafile(RECORD, "r+", SCOREPREFIX);
605 #else
606 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
607 #endif
609 if (!rfile) {
610 HUP raw_print("Cannot open record file!");
611 unlock_file(RECORD);
612 goto destroywin;
615 HUP topten_print("");
617 /* assure minimum number of points */
618 if (t0->points < sysopt.pointsmin)
619 t0->points = 0;
621 t1 = tt_head = newttentry();
622 tprev = 0;
623 /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
624 for (rank = 1;;) {
625 readentry(rfile, t1);
626 if (t1->points < sysopt.pointsmin)
627 t1->points = 0;
628 if (rank0 < 0 && t1->points < t0->points) {
629 rank0 = rank++;
630 if (tprev == 0)
631 tt_head = t0;
632 else
633 tprev->tt_next = t0;
634 t0->tt_next = t1;
635 #ifdef UPDATE_RECORD_IN_PLACE
636 t0->fpos = t1->fpos; /* insert here */
637 #endif
638 t0_used = TRUE;
639 occ_cnt--;
640 flg++; /* ask for a rewrite */
641 } else
642 tprev = t1;
644 if (t1->points == 0)
645 break;
646 if ((sysopt.pers_is_uid ? t1->uid == t0->uid
647 : strncmp(t1->name, t0->name, NAMSZ) == 0)
648 && !strncmp(t1->plrole, t0->plrole, ROLESZ) && --occ_cnt <= 0) {
649 if (rank0 < 0) {
650 rank0 = 0;
651 rank1 = rank;
652 HUP {
653 char pbuf[BUFSZ];
655 Sprintf(pbuf,
656 "You didn't beat your previous score of %ld points.",
657 t1->points);
658 topten_print(pbuf);
659 topten_print("");
662 if (occ_cnt < 0) {
663 flg++;
664 continue;
667 if (rank <= sysopt.entrymax) {
668 t1->tt_next = newttentry();
669 t1 = t1->tt_next;
670 rank++;
672 if (rank > sysopt.entrymax) {
673 t1->points = 0;
674 break;
677 if (flg) { /* rewrite record file */
678 #ifdef UPDATE_RECORD_IN_PLACE
679 (void) fseek(rfile, (t0->fpos >= 0 ? t0->fpos : final_fpos),
680 SEEK_SET);
681 #else
682 (void) fclose(rfile);
683 if (!(rfile = fopen_datafile(RECORD, "w", SCOREPREFIX))) {
684 HUP raw_print("Cannot write record file");
685 unlock_file(RECORD);
686 free_ttlist(tt_head);
687 goto destroywin;
689 #endif /* UPDATE_RECORD_IN_PLACE */
690 if (!done_stopprint)
691 if (rank0 > 0) {
692 if (rank0 <= 10) {
693 topten_print("You made the top ten list!");
694 } else {
695 char pbuf[BUFSZ];
697 Sprintf(pbuf,
698 "You reached the %d%s place on the top %d list.",
699 rank0, ordin(rank0), sysopt.entrymax);
700 topten_print(pbuf);
702 topten_print("");
705 if (rank0 == 0)
706 rank0 = rank1;
707 if (rank0 <= 0)
708 rank0 = rank;
709 if (!done_stopprint)
710 outheader();
711 t1 = tt_head;
712 for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
713 if (flg
714 #ifdef UPDATE_RECORD_IN_PLACE
715 && rank >= rank0
716 #endif
718 writeentry(rfile, t1);
719 if (done_stopprint)
720 continue;
721 if (rank > flags.end_top && (rank < rank0 - flags.end_around
722 || rank > rank0 + flags.end_around)
723 && (!flags.end_own
724 || (sysopt.pers_is_uid
725 ? t1->uid == t0->uid
726 : strncmp(t1->name, t0->name, NAMSZ) == 0)))
727 continue;
728 if (rank == rank0 - flags.end_around
729 && rank0 > flags.end_top + flags.end_around + 1 && !flags.end_own)
730 topten_print("");
731 if (rank != rank0)
732 outentry(rank, t1, FALSE);
733 else if (!rank1)
734 outentry(rank, t1, TRUE);
735 else {
736 outentry(rank, t1, TRUE);
737 outentry(0, t0, TRUE);
740 if (rank0 >= rank)
741 if (!done_stopprint)
742 outentry(0, t0, TRUE);
743 #ifdef UPDATE_RECORD_IN_PLACE
744 if (flg) {
745 #ifdef TRUNCATE_FILE
746 /* if a reasonable way to truncate a file exists, use it */
747 truncate_file(rfile);
748 #else
749 /* use sentinel record rather than relying on truncation */
750 t1->points = 0L; /* terminates file when read back in */
751 t1->ver_major = t1->ver_minor = t1->patchlevel = 0;
752 t1->uid = t1->deathdnum = t1->deathlev = 0;
753 t1->maxlvl = t1->hp = t1->maxhp = t1->deaths = 0;
754 t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-';
755 t1->plrole[1] = t1->plrace[1] = t1->plgend[1] = t1->plalign[1] = 0;
756 t1->birthdate = t1->deathdate = yyyymmdd((time_t) 0L);
757 Strcpy(t1->name, "@");
758 Strcpy(t1->death, "<eod>\n");
759 writeentry(rfile, t1);
760 (void) fflush(rfile);
761 #endif /* TRUNCATE_FILE */
763 #endif /* UPDATE_RECORD_IN_PLACE */
764 (void) fclose(rfile);
765 unlock_file(RECORD);
766 free_ttlist(tt_head);
768 showwin:
769 if (iflags.toptenwin && !done_stopprint)
770 display_nhwindow(toptenwin, 1);
771 destroywin:
772 if (!t0_used)
773 dealloc_ttentry(t0);
774 if (iflags.toptenwin) {
775 destroy_nhwindow(toptenwin);
776 toptenwin = WIN_ERR;
780 STATIC_OVL void
781 outheader()
783 char linebuf[BUFSZ];
784 register char *bp;
786 Strcpy(linebuf, " No Points Name");
787 bp = eos(linebuf);
788 while (bp < linebuf + COLNO - 9)
789 *bp++ = ' ';
790 Strcpy(bp, "Hp [max]");
791 topten_print(linebuf);
794 /* so>0: standout line; so=0: ordinary line */
795 STATIC_OVL void
796 outentry(rank, t1, so)
797 struct toptenentry *t1;
798 int rank;
799 boolean so;
801 boolean second_line = TRUE;
802 char linebuf[BUFSZ];
803 char *bp, hpbuf[24], linebuf3[BUFSZ];
804 int hppos, lngr;
806 linebuf[0] = '\0';
807 if (rank)
808 Sprintf(eos(linebuf), "%3d", rank);
809 else
810 Strcat(linebuf, " ");
812 Sprintf(eos(linebuf), " %10ld %.10s", t1->points ? t1->points : u.urexp,
813 t1->name);
814 Sprintf(eos(linebuf), "-%s", t1->plrole);
815 if (t1->plrace[0] != '?')
816 Sprintf(eos(linebuf), "-%s", t1->plrace);
817 /* Printing of gender and alignment is intentional. It has been
818 * part of the aNetHack Geek Code, and illustrates a proper way to
819 * specify a character from the command line.
821 Sprintf(eos(linebuf), "-%s", t1->plgend);
822 if (t1->plalign[0] != '?')
823 Sprintf(eos(linebuf), "-%s ", t1->plalign);
824 else
825 Strcat(linebuf, " ");
826 if (!strncmp("escaped", t1->death, 7)) {
827 Sprintf(eos(linebuf), "escaped the dungeon %s[max level %d]",
828 !strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "",
829 t1->maxlvl);
830 /* fixup for closing paren in "escaped... with...Amulet)[max..." */
831 if ((bp = index(linebuf, ')')) != 0)
832 *bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' ';
833 second_line = FALSE;
834 } else if (!strncmp("ascended", t1->death, 8)) {
835 Sprintf(eos(linebuf), "ascended to demigod%s-hood",
836 (t1->plgend[0] == 'F') ? "dess" : "");
837 second_line = FALSE;
838 } else {
839 if (!strncmp(t1->death, "quit", 4)) {
840 Strcat(linebuf, "quit");
841 second_line = FALSE;
842 } else if (!strncmp(t1->death, "died of st", 10)) {
843 Strcat(linebuf, "starved to death");
844 second_line = FALSE;
845 } else if (!strncmp(t1->death, "choked", 6)) {
846 Sprintf(eos(linebuf), "choked on h%s food",
847 (t1->plgend[0] == 'F') ? "er" : "is");
848 } else if (!strncmp(t1->death, "poisoned", 8)) {
849 Strcat(linebuf, "was poisoned");
850 } else if (!strncmp(t1->death, "crushed", 7)) {
851 Strcat(linebuf, "was crushed to death");
852 } else if (!strncmp(t1->death, "petrified by ", 13)) {
853 Strcat(linebuf, "turned to stone");
854 } else
855 Strcat(linebuf, "died");
857 if (t1->deathdnum == astral_level.dnum) {
858 const char *arg, *fmt = " on the Plane of %s";
860 switch (t1->deathlev) {
861 case -5:
862 fmt = " on the %s Plane";
863 arg = "Astral";
864 break;
865 case -4:
866 arg = "Water";
867 break;
868 case -3:
869 arg = "Fire";
870 break;
871 case -2:
872 arg = "Air";
873 break;
874 case -1:
875 arg = "Earth";
876 break;
877 default:
878 arg = "Void";
879 break;
881 Sprintf(eos(linebuf), fmt, arg);
882 } else {
883 Sprintf(eos(linebuf), " in %s", dungeons[t1->deathdnum].dname);
884 if (t1->deathdnum != knox_level.dnum)
885 Sprintf(eos(linebuf), " on level %d", t1->deathlev);
886 if (t1->deathlev != t1->maxlvl)
887 Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
890 /* kludge for "quit while already on Charon's boat" */
891 if (!strncmp(t1->death, "quit ", 5))
892 Strcat(linebuf, t1->death + 4);
894 Strcat(linebuf, ".");
896 /* Quit, starved, ascended, and escaped contain no second line */
897 if (second_line)
898 Sprintf(eos(linebuf), " %c%s.", highc(*(t1->death)), t1->death + 1);
900 lngr = (int) strlen(linebuf);
901 if (t1->hp <= 0)
902 hpbuf[0] = '-', hpbuf[1] = '\0';
903 else
904 Sprintf(hpbuf, "%d", t1->hp);
905 /* beginning of hp column after padding (not actually padded yet) */
906 hppos = COLNO - (sizeof(" Hp [max]") - 1); /* sizeof(str) includes \0 */
907 while (lngr >= hppos) {
908 for (bp = eos(linebuf); !(*bp == ' ' && (bp - linebuf < hppos)); bp--)
910 /* special case: word is too long, wrap in the middle */
911 if (linebuf + 15 >= bp)
912 bp = linebuf + hppos - 1;
913 /* special case: if about to wrap in the middle of maximum
914 dungeon depth reached, wrap in front of it instead */
915 if (bp > linebuf + 5 && !strncmp(bp - 5, " [max", 5))
916 bp -= 5;
917 if (*bp != ' ')
918 Strcpy(linebuf3, bp);
919 else
920 Strcpy(linebuf3, bp + 1);
921 *bp = 0;
922 if (so) {
923 while (bp < linebuf + (COLNO - 1))
924 *bp++ = ' ';
925 *bp = 0;
926 topten_print_bold(linebuf);
927 } else
928 topten_print(linebuf);
929 Sprintf(linebuf, "%15s %s", "", linebuf3);
930 lngr = strlen(linebuf);
932 /* beginning of hp column not including padding */
933 hppos = COLNO - 7 - (int) strlen(hpbuf);
934 bp = eos(linebuf);
936 if (bp <= linebuf + hppos) {
937 /* pad any necessary blanks to the hit point entry */
938 while (bp < linebuf + hppos)
939 *bp++ = ' ';
940 Strcpy(bp, hpbuf);
941 Sprintf(eos(bp), " %s[%d]",
942 (t1->maxhp < 10) ? " " : (t1->maxhp < 100) ? " " : "",
943 t1->maxhp);
946 if (so) {
947 bp = eos(linebuf);
948 if (so >= COLNO)
949 so = COLNO - 1;
950 while (bp < linebuf + so)
951 *bp++ = ' ';
952 *bp = 0;
953 topten_print_bold(linebuf);
954 } else
955 topten_print(linebuf);
958 STATIC_OVL int
959 score_wanted(current_ver, rank, t1, playerct, players, uid)
960 boolean current_ver;
961 int rank;
962 struct toptenentry *t1;
963 int playerct;
964 const char **players;
965 int uid;
967 int i;
969 if (current_ver
970 && (t1->ver_major != VERSION_MAJOR || t1->ver_minor != VERSION_MINOR
971 || t1->patchlevel != PATCHLEVEL))
972 return 0;
974 if (sysopt.pers_is_uid && !playerct && t1->uid == uid)
975 return 1;
977 for (i = 0; i < playerct; i++) {
978 if (players[i][0] == '-' && index("pr", players[i][1])
979 && players[i][2] == 0 && i + 1 < playerct) {
980 const char *arg = players[i + 1];
981 if ((players[i][1] == 'p'
982 && str2role(arg) == str2role(t1->plrole))
983 || (players[i][1] == 'r'
984 && str2race(arg) == str2race(t1->plrace)))
985 return 1;
986 i++;
987 } else if (strcmp(players[i], "all") == 0
988 || strncmp(t1->name, players[i], NAMSZ) == 0
989 || (players[i][0] == '-' && players[i][1] == t1->plrole[0]
990 && players[i][2] == 0)
991 || (digit(players[i][0]) && rank <= atoi(players[i])))
992 return 1;
994 return 0;
998 * print selected parts of score list.
999 * argc >= 2, with argv[0] untrustworthy (directory names, et al.),
1000 * and argv[1] starting with "-s".
1002 void
1003 prscore(argc, argv)
1004 int argc;
1005 char **argv;
1007 const char **players;
1008 int playerct, rank;
1009 boolean current_ver = TRUE, init_done = FALSE;
1010 register struct toptenentry *t1;
1011 FILE *rfile;
1012 boolean match_found = FALSE;
1013 register int i;
1014 char pbuf[BUFSZ];
1015 int uid = -1;
1016 const char *player0;
1018 if (argc < 2 || strncmp(argv[1], "-s", 2)) {
1019 raw_printf("prscore: bad arguments (%d)", argc);
1020 return;
1023 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
1024 if (!rfile) {
1025 raw_print("Cannot open record file!");
1026 return;
1029 #ifdef AMIGA
1031 extern winid amii_rawprwin;
1033 init_nhwindows(&argc, argv);
1034 amii_rawprwin = create_nhwindow(NHW_TEXT);
1036 #endif
1038 /* If the score list isn't after a game, we never went through
1039 * initialization. */
1040 if (wiz1_level.dlevel == 0) {
1041 dlb_init();
1042 init_dungeons();
1043 init_done = TRUE;
1046 if (!argv[1][2]) { /* plain "-s" */
1047 argc--;
1048 argv++;
1049 } else
1050 argv[1] += 2;
1052 if (argc > 1 && !strcmp(argv[1], "-v")) {
1053 current_ver = FALSE;
1054 argc--;
1055 argv++;
1058 if (argc <= 1) {
1059 if (sysopt.pers_is_uid) {
1060 uid = getuid();
1061 playerct = 0;
1062 players = (const char **) 0;
1063 } else {
1064 player0 = plname;
1065 if (!*player0)
1066 #ifdef AMIGA
1067 player0 = "all"; /* single user system */
1068 #else
1069 player0 = "hackplayer";
1070 #endif
1071 playerct = 1;
1072 players = &player0;
1074 } else {
1075 playerct = --argc;
1076 players = (const char **) ++argv;
1078 raw_print("");
1080 t1 = tt_head = newttentry();
1081 for (rank = 1;; rank++) {
1082 readentry(rfile, t1);
1083 if (t1->points == 0)
1084 break;
1085 if (!match_found
1086 && score_wanted(current_ver, rank, t1, playerct, players, uid))
1087 match_found = TRUE;
1088 t1->tt_next = newttentry();
1089 t1 = t1->tt_next;
1092 (void) fclose(rfile);
1093 if (init_done) {
1094 free_dungeons();
1095 dlb_cleanup();
1098 if (match_found) {
1099 outheader();
1100 t1 = tt_head;
1101 for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
1102 if (score_wanted(current_ver, rank, t1, playerct, players, uid))
1103 (void) outentry(rank, t1, FALSE);
1105 } else {
1106 Sprintf(pbuf, "Cannot find any %sentries for ",
1107 current_ver ? "current " : "");
1108 if (playerct < 1)
1109 Strcat(pbuf, "you.");
1110 else {
1111 if (playerct > 1)
1112 Strcat(pbuf, "any of ");
1113 for (i = 0; i < playerct; i++) {
1114 /* stop printing players if there are too many to fit */
1115 if (strlen(pbuf) + strlen(players[i]) + 2 >= BUFSZ) {
1116 if (strlen(pbuf) < BUFSZ - 4)
1117 Strcat(pbuf, "...");
1118 else
1119 Strcpy(pbuf + strlen(pbuf) - 4, "...");
1120 break;
1122 Strcat(pbuf, players[i]);
1123 if (i < playerct - 1) {
1124 if (players[i][0] == '-' && index("pr", players[i][1])
1125 && players[i][2] == 0)
1126 Strcat(pbuf, " ");
1127 else
1128 Strcat(pbuf, ":");
1132 raw_print(pbuf);
1133 raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
1135 hname);
1136 raw_printf("Player types are: [-p role] [-r race]");
1138 free_ttlist(tt_head);
1139 #ifdef AMIGA
1141 extern winid amii_rawprwin;
1143 display_nhwindow(amii_rawprwin, 1);
1144 destroy_nhwindow(amii_rawprwin);
1145 amii_rawprwin = WIN_ERR;
1147 #endif
1150 STATIC_OVL int
1151 classmon(plch, fem)
1152 char *plch;
1153 boolean fem;
1155 int i;
1157 /* Look for this role in the role table */
1158 for (i = 0; roles[i].name.m; i++)
1159 if (!strncmp(plch, roles[i].filecode, ROLESZ)) {
1160 if (fem && roles[i].femalenum != NON_PM)
1161 return roles[i].femalenum;
1162 else if (roles[i].malenum != NON_PM)
1163 return roles[i].malenum;
1164 else
1165 return PM_HUMAN;
1167 /* this might be from a 3.2.x score for former Elf class */
1168 if (!strcmp(plch, "E"))
1169 return PM_RANGER;
1171 impossible("What weird role is this? (%s)", plch);
1172 return PM_HUMAN_MUMMY;
1176 * Get a random player name and class from the high score list,
1178 struct toptenentry *
1179 get_rnd_toptenentry()
1181 int rank, i;
1182 FILE *rfile;
1183 register struct toptenentry *tt;
1184 static struct toptenentry tt_buf;
1186 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
1187 if (!rfile) {
1188 impossible("Cannot open record file!");
1189 return NULL;
1192 tt = &tt_buf;
1193 rank = rnd(sysopt.tt_oname_maxrank);
1194 pickentry:
1195 for (i = rank; i; i--) {
1196 readentry(rfile, tt);
1197 if (tt->points == 0)
1198 break;
1201 if (tt->points == 0) {
1202 if (rank > 1) {
1203 rank = 1;
1204 rewind(rfile);
1205 goto pickentry;
1207 tt = NULL;
1210 (void) fclose(rfile);
1211 return tt;
1216 * Attach random player name and class from high score list
1217 * to an object (for statues or morgue corpses).
1219 struct obj *
1220 tt_oname(otmp)
1221 struct obj *otmp;
1223 struct toptenentry *tt;
1224 if (!otmp)
1225 return (struct obj *) 0;
1227 tt = get_rnd_toptenentry();
1229 if (!tt)
1230 return (struct obj *) 0;
1232 set_corpsenm(otmp, classmon(tt->plrole, (tt->plgend[0] == 'F')));
1233 otmp = oname(otmp, tt->name);
1235 return otmp;
1238 #ifdef NO_SCAN_BRACK
1239 /* Lattice scanf isn't up to reading the scorefile. What */
1240 /* follows deals with that; I admit it's ugly. (KL) */
1241 /* Now generally available (KL) */
1242 STATIC_OVL void
1243 nsb_mung_line(p)
1244 char *p;
1246 while ((p = index(p, ' ')) != 0)
1247 *p = '|';
1250 STATIC_OVL void
1251 nsb_unmung_line(p)
1252 char *p;
1254 while ((p = index(p, '|')) != 0)
1255 *p = ' ';
1257 #endif /* NO_SCAN_BRACK */
1259 /*topten.c*/