make rank() static again
[NetHack.git] / src / topten.c
blobcb968a726068a4eac53e1cc1e22aa5f1b2dd4978
1 /* NetHack 3.7 topten.c $NHDT-Date: 1606009004 2020/11/22 01:36:44 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.74 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2012. */
4 /* NetHack may be freely redistributed. See license for details. */
6 #include "hack.h"
7 #include "dlb.h"
9 #if defined(VMS) && !defined(UPDATE_RECORD_IN_PLACE)
10 /* We don't want to rewrite the whole file, because that entails
11 creating a new version which requires that the old one be deletable.
12 [Write and Delete are separate permissions on VMS. 'record' should
13 be writable but not deletable there.] */
14 #define UPDATE_RECORD_IN_PLACE
15 #endif
18 * Updating in place can leave junk at the end of the file in some
19 * circumstances (if it shrinks and the O.S. doesn't have a straightforward
20 * way to truncate it). The trailing junk is harmless and the code
21 * which reads the scores will ignore it.
23 #ifdef UPDATE_RECORD_IN_PLACE
24 static long final_fpos; /* [note: do not move this to the 'g' struct] */
25 #endif
27 #define done_stopprint program_state.stopprint
29 #define newttentry() (struct toptenentry *) alloc(sizeof (struct toptenentry))
30 #define dealloc_ttentry(ttent) free((genericptr_t) (ttent))
31 #ifndef NAMSZ
32 /* Changing NAMSZ can break your existing record/logfile */
33 #define NAMSZ 10
34 #endif
35 #define DTHSZ 100
36 #define ROLESZ 3
38 struct toptenentry {
39 struct toptenentry *tt_next;
40 #ifdef UPDATE_RECORD_IN_PLACE
41 long fpos;
42 #endif
43 long points;
44 int deathdnum, deathlev;
45 int maxlvl, hp, maxhp, deaths;
46 int ver_major, ver_minor, patchlevel;
47 long deathdate, birthdate;
48 int uid;
49 char plrole[ROLESZ + 1];
50 char plrace[ROLESZ + 1];
51 char plgend[ROLESZ + 1];
52 char plalign[ROLESZ + 1];
53 char name[NAMSZ + 1];
54 char death[DTHSZ + 1];
56 static struct toptenentry *tt_head;
57 /* size big enough to read in all the string fields at once; includes
58 room for separating space or trailing newline plus string terminator */
59 #define SCANBUFSZ (4 * (ROLESZ + 1) + (NAMSZ + 1) + (DTHSZ + 1) + 1)
61 static struct toptenentry zerott;
63 staticfn void topten_print(const char *);
64 staticfn void topten_print_bold(const char *);
65 staticfn void outheader(void);
66 staticfn void outentry(int, struct toptenentry *, boolean);
67 staticfn void discardexcess(FILE *);
68 staticfn void readentry(FILE *, struct toptenentry *);
69 staticfn void writeentry(FILE *, struct toptenentry *);
70 #ifdef XLOGFILE
71 staticfn void writexlentry(FILE *, struct toptenentry *, int);
72 staticfn long encodexlogflags(void);
73 staticfn long encodeconduct(void);
74 staticfn long encodeachieve(boolean);
75 staticfn void add_achieveX(char *, const char *, boolean);
76 staticfn char *encode_extended_achievements(char *);
77 staticfn char *encode_extended_conducts(char *);
78 #endif
79 staticfn void free_ttlist(struct toptenentry *);
80 staticfn int classmon(char *);
81 staticfn int score_wanted(boolean, int, struct toptenentry *, int,
82 const char **, int);
83 #ifdef NO_SCAN_BRACK
84 staticfn void nsb_mung_line(char *);
85 staticfn void nsb_unmung_line(char *);
86 #endif
88 /* "killed by",&c ["an"] 'svk.killer.name' */
89 void
90 formatkiller(
91 char *buf,
92 unsigned siz,
93 int how,
94 boolean incl_helpless)
96 static NEARDATA const char *const killed_by_prefix[] = {
97 /* DIED, CHOKING, POISONING, STARVING, */
98 "killed by ", "choked on ", "poisoned by ", "died of ",
99 /* DROWNING, BURNING, DISSOLVED, CRUSHING, */
100 "drowned in ", "burned by ", "dissolved in ", "crushed to death by ",
101 /* STONING, TURNED_SLIME, GENOCIDED, */
102 "petrified by ", "turned to slime by ", "killed by ",
103 /* PANICKED, TRICKED, QUIT, ESCAPED, ASCENDED */
104 "", "", "", "", ""
106 unsigned l;
107 char c, *kname = svk.killer.name;
109 buf[0] = '\0'; /* lint suppression */
110 switch (svk.killer.format) {
111 default:
112 impossible("bad killer format? (%d)", svk.killer.format);
113 FALLTHROUGH;
114 /*FALLTHRU*/
115 case NO_KILLER_PREFIX:
116 break;
117 case KILLED_BY_AN:
118 kname = an(kname);
119 FALLTHROUGH;
120 /*FALLTHRU*/
121 case KILLED_BY:
122 (void) strncat(buf, killed_by_prefix[how], siz - 1);
123 l = Strlen(buf);
124 buf += l, siz -= l;
125 break;
127 /* Copy kname into buf[].
128 * Object names and named fruit have already been sanitized, but
129 * monsters can have "called 'arbitrary text'" attached to them,
130 * so make sure that that text can't confuse field splitting when
131 * record, logfile, or xlogfile is re-read at some later point.
133 while (--siz > 0) {
134 c = *kname++;
135 if (!c)
136 break;
137 else if (c == ',')
138 c = ';';
139 /* 'xlogfile' doesn't really need protection for '=', but
140 fixrecord.awk for corrupted 3.6.0 'record' does (only
141 if using xlogfile rather than logfile to repair record) */
142 else if (c == '=')
143 c = '_';
144 /* tab is not possible due to use of mungspaces() when naming;
145 it would disrupt xlogfile parsing if it were present */
146 else if (c == '\t')
147 c = ' ';
148 *buf++ = c;
150 *buf = '\0';
152 if (incl_helpless && gm.multi < 0) {
153 /* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */
154 if (gm.multi_reason
155 && strlen(gm.multi_reason) + sizeof ", while " <= siz)
156 Sprintf(buf, ", while %s", gm.multi_reason);
157 /* either gm.multi_reason wasn't specified or wouldn't fit */
158 else if (sizeof ", while helpless" <= siz)
159 Strcpy(buf, ", while helpless");
160 /* else extra death info won't fit, so leave it out */
164 staticfn void
165 topten_print(const char *x)
167 if (gt.toptenwin == WIN_ERR)
168 raw_print(x);
169 else
170 putstr(gt.toptenwin, ATR_NONE, x);
173 staticfn void
174 topten_print_bold(const char *x)
176 if (gt.toptenwin == WIN_ERR)
177 raw_print_bold(x);
178 else
179 putstr(gt.toptenwin, ATR_BOLD, x);
183 observable_depth(d_level *lev)
185 #if 0
186 /* if we ever randomize the order of the elemental planes, we
187 must use a constant external representation in the record file */
188 if (In_endgame(lev)) {
189 if (Is_astralevel(lev))
190 return -5;
191 else if (Is_waterlevel(lev))
192 return -4;
193 else if (Is_firelevel(lev))
194 return -3;
195 else if (Is_airlevel(lev))
196 return -2;
197 else if (Is_earthlevel(lev))
198 return -1;
199 else
200 return 0; /* ? */
201 } else
202 #endif
203 return depth(lev);
206 /* throw away characters until current record has been entirely consumed */
207 staticfn void
208 discardexcess(FILE *rfile)
210 int c;
212 do {
213 c = fgetc(rfile);
214 } while (c != '\n' && c != EOF);
217 DISABLE_WARNING_FORMAT_NONLITERAL
219 staticfn void
220 readentry(FILE *rfile, struct toptenentry *tt)
222 char inbuf[SCANBUFSZ], s1[SCANBUFSZ], s2[SCANBUFSZ], s3[SCANBUFSZ],
223 s4[SCANBUFSZ], s5[SCANBUFSZ], s6[SCANBUFSZ];
225 #ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
226 static const char fmt[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c";
227 static const char fmt32[] = "%c%c %s %s%*c";
228 static const char fmt33[] = "%s %s %s %s %s %s%*c";
229 #else
230 static const char fmt[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
231 static const char fmt32[] = "%c%c %[^,],%[^\n]%*c";
232 static const char fmt33[] = "%s %s %s %s %[^,],%[^\n]%*c";
233 #endif
235 #ifdef UPDATE_RECORD_IN_PLACE
236 /* note: input below must read the record's terminating newline */
237 final_fpos = tt->fpos = ftell(rfile);
238 #endif
239 #define TTFIELDS 13
240 if (fscanf(rfile, fmt, &tt->ver_major, &tt->ver_minor, &tt->patchlevel,
241 &tt->points, &tt->deathdnum, &tt->deathlev, &tt->maxlvl,
242 &tt->hp, &tt->maxhp, &tt->deaths, &tt->deathdate,
243 &tt->birthdate, &tt->uid) != TTFIELDS) {
244 #undef TTFIELDS
245 tt->points = 0;
246 discardexcess(rfile);
247 } else {
248 /* load remainder of record into a local buffer;
249 this imposes an implicit length limit of SCANBUFSZ
250 on every string field extracted from the buffer */
251 if (!fgets(inbuf, sizeof inbuf, rfile)) {
252 /* sscanf will fail and tt->points will be set to 0 */
253 *inbuf = '\0';
254 } else if (!strchr(inbuf, '\n')) {
255 Strcpy(&inbuf[sizeof inbuf - 2], "\n");
256 discardexcess(rfile);
258 /* Check for backwards compatibility */
259 if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3)) {
260 int i;
262 if (sscanf(inbuf, fmt32, tt->plrole, tt->plgend, s1, s2) == 4) {
263 tt->plrole[1] = tt->plgend[1] = '\0'; /* read via %c */
264 copynchars(tt->name, s1, (int) (sizeof tt->name) - 1);
265 copynchars(tt->death, s2, (int) (sizeof tt->death) - 1);
266 } else
267 tt->points = 0;
268 tt->plrole[1] = '\0';
269 if ((i = str2role(tt->plrole)) >= 0)
270 Strcpy(tt->plrole, roles[i].filecode);
271 Strcpy(tt->plrace, "?");
272 Strcpy(tt->plgend, (tt->plgend[0] == 'M') ? "Mal" : "Fem");
273 Strcpy(tt->plalign, "?");
274 } else if (sscanf(inbuf, fmt33, s1, s2, s3, s4, s5, s6) == 6) {
275 copynchars(tt->plrole, s1, (int) (sizeof tt->plrole) - 1);
276 copynchars(tt->plrace, s2, (int) (sizeof tt->plrace) - 1);
277 copynchars(tt->plgend, s3, (int) (sizeof tt->plgend) - 1);
278 copynchars(tt->plalign, s4, (int) (sizeof tt->plalign) - 1);
279 copynchars(tt->name, s5, (int) (sizeof tt->name) - 1);
280 copynchars(tt->death, s6, (int) (sizeof tt->death) - 1);
281 } else
282 tt->points = 0;
283 #ifdef NO_SCAN_BRACK
284 if (tt->points > 0) {
285 nsb_unmung_line(tt->name);
286 nsb_unmung_line(tt->death);
288 #endif
291 /* check old score entries for Y2K problem and fix whenever found */
292 if (tt->points > 0) {
293 if (tt->birthdate < 19000000L)
294 tt->birthdate += 19000000L;
295 if (tt->deathdate < 19000000L)
296 tt->deathdate += 19000000L;
300 staticfn void
301 writeentry(FILE *rfile, 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 RESTORE_WARNING_FORMAT_NONLITERAL
336 #ifdef XLOGFILE
338 /* as tab is never used in eg. svp.plname or death, no need to mangle those. */
339 staticfn void
340 writexlentry(FILE *rfile, struct toptenentry *tt, int how)
342 #define Fprintf (void) fprintf
343 #define XLOG_SEP '\t' /* xlogfile field separator. */
344 char buf[BUFSZ], tmpbuf[DTHSZ + 1];
345 char achbuf[N_ACH * 40];
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, svp.plname, XLOG_SEP, tmpbuf);
365 if (gm.multi < 0)
366 Fprintf(rfile, "%cwhile=%s", XLOG_SEP,
367 gm.multi_reason ? gm.multi_reason : "helpless");
368 Fprintf(rfile, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP,
369 encodeconduct(), XLOG_SEP, svm.moves, XLOG_SEP,
370 encodeachieve(FALSE));
371 Fprintf(rfile, "%cachieveX=%s", XLOG_SEP,
372 encode_extended_achievements(achbuf));
373 Fprintf(rfile, "%cconductX=%s", XLOG_SEP,
374 encode_extended_conducts(buf)); /* reuse 'buf[]' */
375 Fprintf(rfile, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP,
376 urealtime.realtime, XLOG_SEP,
377 timet_to_seconds(ubirthday), XLOG_SEP,
378 timet_to_seconds(urealtime.finish_time));
379 Fprintf(rfile, "%cgender0=%s%calign0=%s", XLOG_SEP,
380 genders[flags.initgend].filecode, XLOG_SEP,
381 aligns[1 - u.ualignbase[A_ORIGINAL]].filecode);
382 Fprintf(rfile, "%cflags=0x%lx", XLOG_SEP, encodexlogflags());
383 Fprintf(rfile, "%cgold=%ld", XLOG_SEP,
384 money_cnt(gi.invent) + hidden_gold(TRUE));
385 Fprintf(rfile, "%cwish_cnt=%ld", XLOG_SEP, u.uconduct.wishes);
386 Fprintf(rfile, "%carti_wish_cnt=%ld", XLOG_SEP, u.uconduct.wisharti);
387 Fprintf(rfile, "%cbones=%ld", XLOG_SEP, u.uroleplay.numbones);
388 Fprintf(rfile, "\n");
389 #undef XLOG_SEP
392 staticfn long
393 encodexlogflags(void)
395 long e = 0L;
397 if (wizard)
398 e |= 1L << 0;
399 if (discover)
400 e |= 1L << 1;
401 if (!u.uroleplay.numbones)
402 e |= 1L << 2;
404 return e;
407 staticfn long
408 encodeconduct(void)
410 long e = 0L;
412 if (!u.uconduct.food)
413 e |= 1L << 0;
414 if (!u.uconduct.unvegan)
415 e |= 1L << 1;
416 if (!u.uconduct.unvegetarian)
417 e |= 1L << 2;
418 if (!u.uconduct.gnostic)
419 e |= 1L << 3;
420 if (!u.uconduct.weaphit)
421 e |= 1L << 4;
422 if (!u.uconduct.killer)
423 e |= 1L << 5;
424 if (!u.uconduct.literate)
425 e |= 1L << 6;
426 if (!u.uconduct.polypiles)
427 e |= 1L << 7;
428 if (!u.uconduct.polyselfs)
429 e |= 1L << 8;
430 if (!u.uconduct.wishes)
431 e |= 1L << 9;
432 if (!u.uconduct.wisharti)
433 e |= 1L << 10;
434 if (!num_genocides())
435 e |= 1L << 11;
436 /* one bit isn't really adequate for sokoban conduct:
437 reporting "obeyed sokoban rules" is misleading if sokoban wasn't
438 completed or at least attempted; however, suppressing that when
439 sokoban was never entered, as we do here, risks reporting
440 "violated sokoban rules" when no such thing occurred; this can
441 be disambiguated in xlogfile post-processors by testing the
442 entered-sokoban bit in the 'achieve' field */
443 if (!u.uconduct.sokocheat && sokoban_in_play())
444 e |= 1L << 12;
445 if (!u.uconduct.pets)
446 e |= 1L << 13;
448 return e;
451 staticfn long
452 encodeachieve(
453 boolean secondlong) /* False: handle achievements 1..31, True: 32..62 */
455 int i, achidx, offset;
456 long r = 0L;
459 * 32: portable limit for 'long'.
460 * Force 32 even on configurations that are using 64 bit longs.
462 * We use signed long and limit ourselves to 31 bits since tools
463 * that post-process xlogfile might not be able to cope with
464 * 'unsigned long'.
466 offset = secondlong ? (32 - 1) : 0;
467 for (i = 0; u.uachieved[i]; ++i) {
468 achidx = u.uachieved[i] - offset;
469 if (achidx > 0 && achidx < 32) /* value 1..31 sets bit 0..30 */
470 r |= 1L << (achidx - 1);
472 return r;
475 /* add the achievement or conduct comma-separated to string */
476 staticfn void
477 add_achieveX(char *buf, const char *achievement, boolean condition)
479 if (condition) {
480 if (buf[0] != '\0') {
481 Strcat(buf, ",");
483 Strcat(buf, achievement);
487 staticfn char *
488 encode_extended_achievements(char *buf)
490 char rnkbuf[40];
491 const char *achievement = NULL;
492 int i, achidx, absidx;
494 buf[0] = '\0';
495 for (i = 0; u.uachieved[i]; i++) {
496 achidx = u.uachieved[i];
497 absidx = abs(achidx);
498 switch (absidx) {
499 case ACH_UWIN:
500 achievement = "ascended";
501 break;
502 case ACH_ASTR:
503 achievement = "entered_astral_plane";
504 break;
505 case ACH_ENDG:
506 achievement = "entered_elemental_planes";
507 break;
508 case ACH_AMUL:
509 achievement = "obtained_the_amulet_of_yendor";
510 break;
511 case ACH_INVK:
512 achievement = "performed_the_invocation_ritual";
513 break;
514 case ACH_BOOK:
515 achievement = "obtained_the_book_of_the_dead";
516 break;
517 case ACH_BELL:
518 achievement = "obtained_the_bell_of_opening";
519 break;
520 case ACH_CNDL:
521 achievement = "obtained_the_candelabrum_of_invocation";
522 break;
523 case ACH_HELL:
524 achievement = "entered_gehennom";
525 break;
526 case ACH_MEDU:
527 achievement = "defeated_medusa";
528 break;
529 case ACH_MINE_PRIZE:
530 achievement = "obtained_the_luckstone_from_the_mines";
531 break;
532 case ACH_SOKO_PRIZE:
533 achievement = "obtained_the_sokoban_prize";
534 break;
535 case ACH_ORCL:
536 achievement = "consulted_the_oracle";
537 break;
538 case ACH_NOVL:
539 achievement = "read_a_discworld_novel";
540 break;
541 case ACH_MINE:
542 achievement = "entered_the_gnomish_mines";
543 break;
544 case ACH_TOWN:
545 achievement = "entered_mine_town";
546 break;
547 case ACH_SHOP:
548 achievement = "entered_a_shop";
549 break;
550 case ACH_TMPL:
551 achievement = "entered_a_temple";
552 break;
553 case ACH_SOKO:
554 achievement = "entered_sokoban";
555 break;
556 case ACH_BGRM:
557 achievement = "entered_bigroom";
558 break;
559 case ACH_TUNE:
560 achievement = "learned_castle_drawbridge_tune";
561 break;
562 /* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */
563 case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4:
564 case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8:
565 Sprintf(rnkbuf, "attained_the_rank_of_%s",
566 rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
567 Role_switch, (achidx < 0) ? TRUE : FALSE));
568 strNsubst(rnkbuf, " ", "_", 0); /* replace every ' ' with '_' */
569 achievement = lcase(rnkbuf);
570 break;
571 default:
572 continue;
574 add_achieveX(buf, achievement, TRUE);
577 return buf;
580 staticfn char *
581 encode_extended_conducts(char *buf)
583 buf[0] = '\0';
584 add_achieveX(buf, "foodless", !u.uconduct.food);
585 add_achieveX(buf, "vegan", !u.uconduct.unvegan);
586 add_achieveX(buf, "vegetarian", !u.uconduct.unvegetarian);
587 add_achieveX(buf, "atheist", !u.uconduct.gnostic);
588 add_achieveX(buf, "weaponless", !u.uconduct.weaphit);
589 add_achieveX(buf, "pacifist", !u.uconduct.killer);
590 add_achieveX(buf, "illiterate", !u.uconduct.literate);
591 add_achieveX(buf, "polyless", !u.uconduct.polypiles);
592 add_achieveX(buf, "polyselfless", !u.uconduct.polyselfs);
593 add_achieveX(buf, "wishless", !u.uconduct.wishes);
594 add_achieveX(buf, "artiwishless", !u.uconduct.wisharti);
595 add_achieveX(buf, "genocideless", !num_genocides());
596 if (sokoban_in_play())
597 add_achieveX(buf, "sokoban", !u.uconduct.sokocheat);
598 add_achieveX(buf, "blind", u.uroleplay.blind);
599 add_achieveX(buf, "deaf", u.uroleplay.deaf);
600 add_achieveX(buf, "nudist", u.uroleplay.nudist);
601 add_achieveX(buf, "pauper", u.uroleplay.pauper);
602 add_achieveX(buf, "bonesless", !flags.bones);
603 add_achieveX(buf, "petless", !u.uconduct.pets);
605 return buf;
608 #endif /* XLOGFILE */
610 staticfn void
611 free_ttlist(struct toptenentry *tt)
613 struct toptenentry *ttnext;
615 while (tt->points > 0) {
616 ttnext = tt->tt_next;
617 dealloc_ttentry(tt);
618 tt = ttnext;
620 dealloc_ttentry(tt);
623 void
624 topten(int how, time_t when)
626 struct toptenentry *t0, *tprev;
627 struct toptenentry *t1;
628 FILE *rfile;
629 #ifdef LOGFILE
630 FILE *lfile;
631 #endif
632 #ifdef XLOGFILE
633 FILE *xlfile;
634 #endif
635 int uid = getuid();
636 int rank, rank0 = -1, rank1 = 0;
637 int occ_cnt = sysopt.persmax;
638 int flg = 0;
639 boolean t0_used, skip_scores;
641 #ifdef UPDATE_RECORD_IN_PLACE
642 final_fpos = 0L;
643 #endif
644 /* If we are in the midst of a panic, cut out topten entirely.
645 * topten uses alloc() several times, which will lead to
646 * problems if the panic was the result of an alloc() failure.
648 if (program_state.panicking)
649 return;
651 if (iflags.toptenwin) {
652 gt.toptenwin = create_nhwindow(NHW_TEXT);
655 #if defined(HANGUPHANDLING)
656 #define HUP if (!program_state.done_hup)
657 #else
658 #define HUP
659 #endif
661 #ifdef TOS
662 restore_colors(); /* make sure the screen is black on white */
663 #endif
664 /* create a new 'topten' entry */
665 t0_used = FALSE;
666 t0 = newttentry();
667 *t0 = zerott;
668 t0->ver_major = VERSION_MAJOR;
669 t0->ver_minor = VERSION_MINOR;
670 t0->patchlevel = PATCHLEVEL;
671 t0->points = u.urexp;
672 t0->deathdnum = u.uz.dnum;
673 /* deepest_lev_reached() is in terms of depth(), and reporting the
674 * deepest level reached in the dungeon death occurred in doesn't
675 * seem right, so we have to report the death level in depth() terms
676 * as well (which also seems reasonable since that's all the player
677 * sees on the screen anyway)
679 t0->deathlev = observable_depth(&u.uz);
680 t0->maxlvl = deepest_lev_reached(TRUE);
681 t0->hp = u.uhp;
682 t0->maxhp = u.uhpmax;
683 t0->deaths = u.umortality;
684 t0->uid = uid;
685 copynchars(t0->plrole, gu.urole.filecode, ROLESZ);
686 copynchars(t0->plrace, gu.urace.filecode, ROLESZ);
687 copynchars(t0->plgend, genders[flags.female].filecode, ROLESZ);
688 copynchars(t0->plalign, aligns[1 - u.ualign.type].filecode, ROLESZ);
689 copynchars(t0->name, svp.plname, NAMSZ);
690 formatkiller(t0->death, sizeof t0->death, how, TRUE);
691 t0->birthdate = yyyymmdd(ubirthday);
692 t0->deathdate = yyyymmdd(when);
693 t0->tt_next = 0;
694 #ifdef UPDATE_RECORD_IN_PLACE
695 t0->fpos = -1L;
696 #endif
698 #ifdef LOGFILE /* used for debugging (who dies of what, where) */
699 if (lock_file(LOGFILE, SCOREPREFIX, 10)) {
700 if (!(lfile = fopen_datafile(LOGFILE, "a", SCOREPREFIX))) {
701 HUP raw_print("Cannot open log file!");
702 } else {
703 writeentry(lfile, t0);
704 (void) fclose(lfile);
706 unlock_file(LOGFILE);
708 #endif /* LOGFILE */
709 #ifdef XLOGFILE
710 if (lock_file(XLOGFILE, SCOREPREFIX, 10)) {
711 if (!(xlfile = fopen_datafile(XLOGFILE, "a", SCOREPREFIX))) {
712 HUP raw_print("Cannot open extended log file!");
713 } else {
714 writexlentry(xlfile, t0, how);
715 (void) fclose(xlfile);
717 unlock_file(XLOGFILE);
719 #endif /* XLOGFILE */
721 if (wizard || discover) {
722 if (how != PANICKED)
723 HUP {
724 char pbuf[BUFSZ];
726 topten_print("");
727 Sprintf(pbuf,
728 "Since you were in %s mode, the score list will not be checked.",
729 wizard ? "wizard" : "discover");
730 topten_print(pbuf);
732 goto showwin;
735 if (!lock_file(RECORD, SCOREPREFIX, 60))
736 goto destroywin;
738 #ifdef UPDATE_RECORD_IN_PLACE
739 rfile = fopen_datafile(RECORD, "r+", SCOREPREFIX);
740 #else
741 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
742 #endif
744 if (!rfile) {
745 HUP raw_print("Cannot open record file!");
746 unlock_file(RECORD);
747 goto destroywin;
750 HUP topten_print("");
752 /* assure minimum number of points */
753 if (t0->points < sysopt.pointsmin)
754 t0->points = 0;
756 t1 = tt_head = newttentry();
757 tprev = 0;
758 /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
759 for (rank = 1; ; ) {
760 readentry(rfile, t1);
761 if (t1->points < sysopt.pointsmin)
762 t1->points = 0;
763 if (rank0 < 0 && t1->points < t0->points) {
764 rank0 = rank++;
765 if (tprev == 0)
766 tt_head = t0;
767 else
768 tprev->tt_next = t0;
769 t0->tt_next = t1;
770 #ifdef UPDATE_RECORD_IN_PLACE
771 t0->fpos = t1->fpos; /* insert here */
772 #endif
773 t0_used = TRUE;
774 occ_cnt--;
775 flg++; /* ask for a rewrite */
776 } else
777 tprev = t1;
779 if (t1->points == 0)
780 break;
781 if ((sysopt.pers_is_uid ? t1->uid == t0->uid
782 : strncmp(t1->name, t0->name, NAMSZ) == 0)
783 && !strncmp(t1->plrole, t0->plrole, ROLESZ) && --occ_cnt <= 0) {
784 if (rank0 < 0) {
785 rank0 = 0;
786 rank1 = rank;
787 HUP {
788 char pbuf[BUFSZ];
790 Sprintf(pbuf,
791 "You didn't beat your previous score of %ld points.",
792 t1->points);
793 topten_print(pbuf);
794 topten_print("");
797 if (occ_cnt < 0) {
798 flg++;
799 continue;
802 if (rank <= sysopt.entrymax) {
803 t1->tt_next = newttentry();
804 t1 = t1->tt_next;
805 rank++;
807 if (rank > sysopt.entrymax) {
808 t1->points = 0;
809 break;
812 if (flg) { /* rewrite record file */
813 #ifdef UPDATE_RECORD_IN_PLACE
814 (void) fseek(rfile, (t0->fpos >= 0) ? t0->fpos : final_fpos, SEEK_SET);
815 #else
816 (void) fclose(rfile);
817 if (!(rfile = fopen_datafile(RECORD, "w", SCOREPREFIX))) {
818 HUP raw_print("Cannot write record file");
819 unlock_file(RECORD);
820 free_ttlist(tt_head);
821 goto destroywin;
823 #endif /* UPDATE_RECORD_IN_PLACE */
824 if (!done_stopprint)
825 if (rank0 > 0) {
826 if (rank0 <= 10) {
827 topten_print("You made the top ten list!");
828 } else {
829 char pbuf[BUFSZ];
831 Sprintf(pbuf,
832 "You reached the %d%s place on the top %d list.",
833 rank0, ordin(rank0), sysopt.entrymax);
834 topten_print(pbuf);
836 topten_print("");
839 skip_scores = !flags.end_top && !flags.end_around && !flags.end_own;
840 if (rank0 == 0)
841 rank0 = rank1;
842 if (rank0 <= 0)
843 rank0 = rank;
844 if (!skip_scores && !done_stopprint)
845 outheader();
846 for (t1 = tt_head, rank = 1; t1->points != 0; t1 = t1->tt_next, ++rank) {
847 if (flg
848 #ifdef UPDATE_RECORD_IN_PLACE
849 && rank >= rank0
850 #endif
852 writeentry(rfile, t1);
853 if (skip_scores || done_stopprint)
854 continue;
855 if (rank <= flags.end_top
856 || (rank >= rank0 - flags.end_around
857 && rank <= rank0 + flags.end_around)
858 || (flags.end_own && (sysopt.pers_is_uid
859 ? t1->uid == t0->uid
860 : !strncmp(t1->name, t0->name, NAMSZ)))) {
861 if (rank == rank0 - flags.end_around
862 && rank0 > flags.end_top + flags.end_around + 1
863 && !flags.end_own)
864 topten_print("");
866 if (rank != rank0) {
867 outentry(rank, t1, FALSE);
868 } else if (!rank1) {
869 outentry(rank, t1, TRUE);
870 } else {
871 outentry(rank, t1, TRUE);
872 outentry(0, t0, TRUE);
876 if (rank0 >= rank)
877 if (!skip_scores && !done_stopprint)
878 outentry(0, t0, TRUE);
879 #ifdef UPDATE_RECORD_IN_PLACE
880 if (flg) {
881 #ifdef TRUNCATE_FILE
882 /* if a reasonable way to truncate a file exists, use it */
883 truncate_file(rfile);
884 #else
885 /* use sentinel record rather than relying on truncation */
886 *t1 = zerott;
887 t1->points = 0L; /* [redundant] terminates file when read back in */
888 t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-';
889 t1->birthdate = t1->deathdate = yyyymmdd((time_t) 0L);
890 Strcpy(t1->name, "@");
891 Strcpy(t1->death, "<eod>\n"); /* end of data */
892 writeentry(rfile, t1);
893 /* note: there might be junk (if file has shrunk due to shorter
894 entries supplanting longer ones) after this dummy entry, but
895 reading and/or updating will ignore it */
896 (void) fflush(rfile);
897 #endif /* TRUNCATE_FILE */
899 #endif /* UPDATE_RECORD_IN_PLACE */
900 (void) fclose(rfile);
901 unlock_file(RECORD);
902 free_ttlist(tt_head);
904 showwin:
905 if (!done_stopprint) {
906 if (iflags.toptenwin) {
907 display_nhwindow(gt.toptenwin, TRUE);
908 } else {
909 /* when not a window, we need something comparable to more()
910 but can't use it directly because we aren't dealing with
911 the message window */
915 destroywin:
916 if (!t0_used)
917 dealloc_ttentry(t0);
918 if (iflags.toptenwin) {
919 destroy_nhwindow(gt.toptenwin);
920 gt.toptenwin = WIN_ERR;
924 staticfn void
925 outheader(void)
927 char linebuf[BUFSZ];
928 char *bp;
930 Strcpy(linebuf, " No Points Name");
931 bp = eos(linebuf);
932 while (bp < linebuf + COLNO - 9)
933 *bp++ = ' ';
934 Strcpy(bp, "Hp [max]");
935 topten_print(linebuf);
938 DISABLE_WARNING_FORMAT_NONLITERAL
940 /* so>0: standout line; so=0: ordinary line */
941 staticfn void
942 outentry(int rank, struct toptenentry *t1, boolean so)
944 boolean second_line = TRUE;
945 char linebuf[BUFSZ];
946 char *bp, hpbuf[24], linebuf3[BUFSZ];
947 int hppos, lngr;
949 linebuf[0] = '\0';
950 if (rank)
951 Sprintf(eos(linebuf), "%3d", rank);
952 else
953 Strcat(linebuf, " ");
955 Sprintf(eos(linebuf), " %10ld %.10s", t1->points ? t1->points : u.urexp,
956 t1->name);
957 Sprintf(eos(linebuf), "-%s", t1->plrole);
958 if (t1->plrace[0] != '?')
959 Sprintf(eos(linebuf), "-%s", t1->plrace);
960 /* Printing of gender and alignment is intentional. It has been
961 * part of the NetHack Geek Code, and illustrates a proper way to
962 * specify a character from the command line.
964 Sprintf(eos(linebuf), "-%s", t1->plgend);
965 if (t1->plalign[0] != '?')
966 Sprintf(eos(linebuf), "-%s ", t1->plalign);
967 else
968 Strcat(linebuf, " ");
969 if (!strncmp("escaped", t1->death, 7)) {
970 Sprintf(eos(linebuf), "escaped the dungeon %s[max level %d]",
971 !strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "",
972 t1->maxlvl);
973 /* fixup for closing paren in "escaped... with...Amulet)[max..." */
974 if ((bp = strchr(linebuf, ')')) != 0)
975 *bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' ';
976 second_line = FALSE;
977 } else if (!strncmp("ascended", t1->death, 8)) {
978 Sprintf(eos(linebuf), "ascended to demigod%s-hood",
979 (t1->plgend[0] == 'F') ? "dess" : "");
980 second_line = FALSE;
981 } else {
982 if (!strncmp(t1->death, "quit", 4)) {
983 Strcat(linebuf, "quit");
984 second_line = FALSE;
985 } else if (!strncmp(t1->death, "died of st", 10)) {
986 Strcat(linebuf, "starved to death");
987 second_line = FALSE;
988 } else if (!strncmp(t1->death, "choked", 6)) {
989 Sprintf(eos(linebuf), "choked on h%s food",
990 (t1->plgend[0] == 'F') ? "er" : "is");
991 } else if (!strncmp(t1->death, "poisoned", 8)) {
992 Strcat(linebuf, "was poisoned");
993 } else if (!strncmp(t1->death, "crushed", 7)) {
994 Strcat(linebuf, "was crushed to death");
995 } else if (!strncmp(t1->death, "petrified by ", 13)) {
996 Strcat(linebuf, "turned to stone");
997 } else
998 Strcat(linebuf, "died");
1000 if (t1->deathdnum == astral_level.dnum) {
1001 const char *arg, *fmt = " on the Plane of %s";
1003 switch (t1->deathlev) {
1004 case -5:
1005 fmt = " on the %s Plane";
1006 arg = "Astral";
1007 break;
1008 case -4:
1009 arg = "Water";
1010 break;
1011 case -3:
1012 arg = "Fire";
1013 break;
1014 case -2:
1015 arg = "Air";
1016 break;
1017 case -1:
1018 arg = "Earth";
1019 break;
1020 default:
1021 arg = "Void";
1022 break;
1024 Sprintf(eos(linebuf), fmt, arg);
1025 } else {
1026 Sprintf(eos(linebuf), " in %s", svd.dungeons[t1->deathdnum].dname);
1027 if (t1->deathdnum != knox_level.dnum)
1028 Sprintf(eos(linebuf), " on level %d", t1->deathlev);
1029 if (t1->deathlev != t1->maxlvl)
1030 Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
1033 /* kludge for "quit while already on Charon's boat" */
1034 if (!strncmp(t1->death, "quit ", 5))
1035 Strcat(linebuf, t1->death + 4);
1037 Strcat(linebuf, ".");
1039 /* Quit, starved, ascended, and escaped contain no second line */
1040 if (second_line) {
1041 bp = eos(linebuf);
1042 Sprintf(bp, " %c%s.", highc(*(t1->death)), t1->death + 1);
1043 /* fix up "Killed by Mr. Asidonhopo; the shopkeeper"; that starts
1044 with a comma but has it changed to semi-colon to keep the comma
1045 out of 'record'; change it back for display */
1046 (void) strsubst(bp, "; the ", ", the ");
1049 lngr = (int) strlen(linebuf);
1050 if (t1->hp <= 0)
1051 hpbuf[0] = '-', hpbuf[1] = '\0';
1052 else
1053 Sprintf(hpbuf, "%d", t1->hp);
1054 /* beginning of hp column after padding (not actually padded yet) */
1055 hppos = COLNO - (int) (sizeof " Hp [max]" - sizeof "");
1056 while (lngr >= hppos) {
1057 for (bp = eos(linebuf); !(*bp == ' ' && bp - linebuf < hppos); bp--)
1059 /* special case: word is too long, wrap in the middle */
1060 if (linebuf + 15 >= bp)
1061 bp = linebuf + hppos - 1;
1062 /* special case: if about to wrap in the middle of maximum
1063 dungeon depth reached, wrap in front of it instead */
1064 if (bp > linebuf + 5 && !strncmp(bp - 5, " [max", 5))
1065 bp -= 5;
1066 if (*bp != ' ')
1067 Strcpy(linebuf3, bp);
1068 else
1069 Strcpy(linebuf3, bp + 1);
1070 *bp = '\0';
1071 if (so) {
1072 while (bp < linebuf + (COLNO - 1))
1073 *bp++ = ' ';
1074 *bp = '\0';
1075 topten_print_bold(linebuf);
1076 } else
1077 topten_print(linebuf);
1078 Snprintf(linebuf, sizeof(linebuf), "%15s %s", "", linebuf3);
1079 lngr = Strlen(linebuf);
1081 /* beginning of hp column not including padding */
1082 hppos = COLNO - 7 - (int) strlen(hpbuf);
1083 bp = eos(linebuf);
1085 if (bp <= linebuf + hppos) {
1086 /* pad any necessary blanks to the hit point entry */
1087 while (bp < linebuf + hppos)
1088 *bp++ = ' ';
1089 Strcpy(bp, hpbuf);
1090 Sprintf(eos(bp), " %s[%d]",
1091 (t1->maxhp < 10) ? " " : (t1->maxhp < 100) ? " " : "",
1092 t1->maxhp);
1095 if (so) {
1096 bp = eos(linebuf);
1097 while (bp < linebuf + (COLNO - 1))
1098 *bp++ = ' ';
1099 *bp = '\0';
1100 topten_print_bold(linebuf);
1101 } else
1102 topten_print(linebuf);
1105 RESTORE_WARNING_FORMAT_NONLITERAL
1107 staticfn int
1108 score_wanted(
1109 boolean current_ver,
1110 int rank,
1111 struct toptenentry *t1,
1112 int playerct,
1113 const char **players,
1114 int uid)
1116 const char *arg, *nxt;
1117 int i;
1119 if (current_ver && (t1->ver_major != VERSION_MAJOR
1120 || t1->ver_minor != VERSION_MINOR
1121 || t1->patchlevel != PATCHLEVEL))
1122 return 0;
1124 if (sysopt.pers_is_uid && !playerct && t1->uid == uid)
1125 return 1;
1128 * FIXME:
1129 * This selection produces a union (OR) of criteria rather than
1130 * an intersection (AND). So
1131 * nethack -s -u igor -p Cav -r Hum
1132 * will list all entries for name igor regardless of role or race
1133 * plus all entries for cave dwellers regardless of name or race
1134 * plus all entries for humans regardless of name or role.
1136 * It would be more useful if it only chose human cave dwellers
1137 * named igor. That would be pretty straightforward if only one
1138 * instance of each of the criteria were possible, but
1139 * nethack -s -u igor -u ayn -p Cav -p Pri -r Hum -r Dwa
1140 * should list human cave dwellers named igor and human cave
1141 * dwellers named ayn plus dwarven cave dwellers named igor and
1142 * dwarven cave dwellers named ayn plus human priest[esse]s named
1143 * igor and human priest[esse]s named ayn (the combination of
1144 * dwarven priest[esse]s doesn't occur but the selection can test
1145 * entries without being aware of such; it just won't find any
1146 * matches for that). An extra initial pass of the command line
1147 * to collect all criteria before testing any entry is needed to
1148 * accomplish this. And we might need to drop support for
1149 * pre-3.3.0 entries (old elf role) depending on how the criteria
1150 * matching is performed.
1152 * It also ought to extended to handle
1153 * nethack -s -u igor-Cav-Hum
1154 * Alignment and gender could be useful too but no one has ever
1155 * clamored for them. Presumably if they care they postprocess
1156 * with some custom tool.
1159 for (i = 0; i < playerct; i++) {
1160 arg = players[i];
1161 if (arg[0] == '-' && arg[1] == 'u' && arg[2] != '\0')
1162 arg += 2; /* handle '-uname' */
1164 if (arg[0] == '-' && strchr("pru", arg[1]) && !arg[2]
1165 && i + 1 < playerct) {
1166 nxt = players[i + 1];
1167 if ((arg[1] == 'p' && str2role(nxt) == str2role(t1->plrole))
1168 || (arg[1] == 'r' && str2race(nxt) == str2race(t1->plrace))
1169 /* handle '-u name' */
1170 || (arg[1] == 'u' && (!strcmp(nxt, "all")
1171 || !strncmp(t1->name, nxt, NAMSZ))))
1172 return 1;
1173 i++;
1174 } else if (!strcmp(arg, "all")
1175 || !strncmp(t1->name, arg, NAMSZ)
1176 || (arg[0] == '-' && arg[1] == t1->plrole[0] && !arg[2])
1177 || (digit(arg[0]) && rank <= atoi(arg)))
1178 return 1;
1180 return 0;
1184 * print selected parts of score list.
1185 * argc >= 2, with argv[0] untrustworthy (directory names, et al.),
1186 * and argv[1] starting with "-s".
1187 * caveat: some shells might allow argv elements to be arbitrarily long.
1189 void
1190 prscore(int argc, char **argv)
1192 const char **players, *player0;
1193 int i, playerct, rank;
1194 struct toptenentry *t1;
1195 FILE *rfile;
1196 char pbuf[BUFSZ], *p;
1197 unsigned ln;
1198 int uid = -1;
1199 boolean current_ver = TRUE, init_done = FALSE, match_found = FALSE;
1201 /* expect "-s" or "--scores"; "-s<anything> is accepted */
1202 ln = (argc < 2) ? 0U
1203 : ((p = strchr(argv[1], ' ')) != 0) ? (unsigned) (p - argv[1])
1204 : Strlen(argv[1]);
1205 if (ln < 2 || (strncmp(argv[1], "-s", 2)
1206 && strcmp(argv[1], "--scores"))) {
1207 raw_printf("prscore: bad arguments (%d)", argc);
1208 return;
1211 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
1212 if (!rfile) {
1213 raw_print("Cannot open record file!");
1214 return;
1217 #ifdef AMIGA
1219 extern winid amii_rawprwin;
1221 init_nhwindows(&argc, argv);
1222 amii_rawprwin = create_nhwindow(NHW_TEXT);
1224 #endif
1226 /* If the score list isn't after a game, we never went through
1227 * initialization. */
1228 if (wiz1_level.dlevel == 0) {
1229 dlb_init();
1230 init_dungeons();
1231 init_done = TRUE;
1234 /* to get here, argv[1] either starts with "-s" or is "--scores" without
1235 trailing stuff; for "-s<anything>" treat <anything> as separate arg */
1236 if (argv[1][1] == '-' || !argv[1][2]) {
1237 argc--;
1238 argv++;
1239 } else { /* concatenated arg string; use up "-s" but keep argc,argv */
1240 argv[1] += 2;
1242 /* -v doesn't take a version number arg; it means 'all versions present
1243 in the file' instead of the default of only the current version;
1244 unlike -s, we don't accept "-v<anything>" for non-empty <anything> */
1245 if (argc > 1 && !strcmp(argv[1], "-v")) {
1246 current_ver = FALSE;
1247 argc--;
1248 argv++;
1251 if (argc <= 1) {
1252 if (sysopt.pers_is_uid) {
1253 uid = getuid();
1254 playerct = 0;
1255 players = (const char **) 0;
1256 } else {
1257 player0 = svp.plname;
1258 if (!*player0)
1259 player0 = "all"; /* if no plname[], show all scores
1260 * (possibly filtered by '-v') */
1261 playerct = 1;
1262 players = &player0;
1264 } else {
1265 playerct = --argc;
1266 players = (const char **) ++argv;
1268 raw_print("");
1270 t1 = tt_head = newttentry();
1271 for (rank = 1; ; rank++) {
1272 readentry(rfile, t1);
1273 if (t1->points == 0)
1274 break;
1275 if (!match_found
1276 && score_wanted(current_ver, rank, t1, playerct, players, uid))
1277 match_found = TRUE;
1278 t1->tt_next = newttentry();
1279 t1 = t1->tt_next;
1282 (void) fclose(rfile);
1283 if (init_done) {
1284 free_dungeons();
1285 dlb_cleanup();
1288 if (match_found) {
1289 outheader();
1290 t1 = tt_head;
1291 for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
1292 if (score_wanted(current_ver, rank, t1, playerct, players, uid))
1293 (void) outentry(rank, t1, FALSE);
1295 } else {
1296 Sprintf(pbuf, "Cannot find any %sentries for ",
1297 current_ver ? "current " : "");
1298 if (playerct < 1) {
1299 Strcat(pbuf, "you");
1300 } else {
1301 /* minor bug: 'nethack -s -u ziggy' will say "any of"
1302 even though the '-u' doesn't indicate multiple names */
1303 if (playerct > 1)
1304 Strcat(pbuf, "any of ");
1305 for (i = 0; i < playerct; i++) {
1306 /* accept '-u name' and '-uname' as well as just 'name'
1307 so skip '-u' for the none-found feedback */
1308 if (!strncmp(players[i], "-u", 2)) {
1309 if (!players[i][2])
1310 continue;
1311 players[i] += 2;
1313 /* stop printing players if there are too many to fit */
1314 if (strlen(pbuf) + strlen(players[i]) + 2 >= BUFSZ) {
1315 if (strlen(pbuf) < BUFSZ - 4)
1316 Strcat(pbuf, "...");
1317 else
1318 Strcpy(pbuf + strlen(pbuf) - 4, "...");
1319 break;
1321 Strcat(pbuf, players[i]);
1322 if (i < playerct - 1) {
1323 if (players[i][0] == '-' && strchr("pr", players[i][1])
1324 && players[i][2] == 0)
1325 Strcat(pbuf, " ");
1326 else
1327 Strcat(pbuf, ":");
1331 /* append end-of-sentence punctuation if there is room */
1332 if (strlen(pbuf) < BUFSZ - 1)
1333 Strcat(pbuf, ".");
1334 raw_print(pbuf);
1335 raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
1336 gh.hname);
1337 raw_printf("Player types are: [-p role] [-r race]");
1339 free_ttlist(tt_head);
1340 #ifdef AMIGA
1342 extern winid amii_rawprwin;
1344 display_nhwindow(amii_rawprwin, 1);
1345 destroy_nhwindow(amii_rawprwin);
1346 amii_rawprwin = WIN_ERR;
1348 #endif
1351 staticfn int
1352 classmon(char *plch)
1354 int i;
1356 /* Look for this role in the role table */
1357 for (i = 0; roles[i].name.m; i++) {
1358 if (!strncmp(plch, roles[i].filecode, ROLESZ)) {
1359 if (roles[i].mnum != NON_PM)
1360 return roles[i].mnum;
1361 else
1362 return PM_HUMAN;
1365 /* this might be from a 3.2.x score for former Elf class */
1366 if (!strcmp(plch, "E"))
1367 return PM_RANGER;
1369 impossible("What weird role is this? (%s)", plch);
1370 return PM_HUMAN_MUMMY;
1374 * Get a random player name and class from the high score list,
1376 struct toptenentry *
1377 get_rnd_toptenentry(void)
1379 int rank, i;
1380 FILE *rfile;
1381 struct toptenentry *tt;
1382 static struct toptenentry tt_buf;
1384 rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
1385 if (!rfile) {
1386 impossible("Cannot open record file!");
1387 return NULL;
1390 tt = &tt_buf;
1391 rank = rnd(sysopt.tt_oname_maxrank);
1392 pickentry:
1393 for (i = rank; i; i--) {
1394 readentry(rfile, tt);
1395 if (tt->points == 0)
1396 break;
1399 if (tt->points == 0) {
1400 if (rank > 1) {
1401 rank = 1;
1402 rewind(rfile);
1403 goto pickentry;
1405 tt = NULL;
1408 (void) fclose(rfile);
1409 return tt;
1414 * Attach random player name and class from high score list
1415 * to an object (for statues or morgue corpses).
1417 struct obj *
1418 tt_oname(struct obj *otmp)
1420 struct toptenentry *tt;
1421 if (!otmp)
1422 return (struct obj *) 0;
1424 tt = get_rnd_toptenentry();
1426 if (!tt)
1427 return (struct obj *) 0;
1429 set_corpsenm(otmp, classmon(tt->plrole));
1430 if (tt->plgend[0] == 'F')
1431 otmp->spe = CORPSTAT_FEMALE;
1432 else if (tt->plgend[0] == 'M')
1433 otmp->spe = CORPSTAT_MALE;
1434 otmp = oname(otmp, tt->name, ONAME_NO_FLAGS);
1436 return otmp;
1439 /* Randomly select a topten entry to mimic */
1441 tt_doppel(struct monst *mon) {
1442 struct toptenentry *tt = rn2(13) ? get_rnd_toptenentry() : NULL;
1443 int ret;
1445 if (!tt)
1446 ret = rn1(PM_WIZARD - PM_ARCHEOLOGIST + 1, PM_ARCHEOLOGIST);
1447 else {
1448 if (tt->plgend[0] == 'F')
1449 mon->female = 1;
1450 else if (tt->plgend[0] == 'M')
1451 mon->female = 0;
1452 ret = classmon(tt->plrole);
1453 /* Only take on a name if the player can see
1454 the doppelganger, otherwise we end up with
1455 named monsters spoiling the fun - Kes */
1456 if (canseemon(mon))
1457 christen_monst(mon, tt->name);
1459 return ret;
1462 #ifdef NO_SCAN_BRACK
1463 /* Lattice scanf isn't up to reading the scorefile. What */
1464 /* follows deals with that; I admit it's ugly. (KL) */
1465 /* Now generally available (KL) */
1466 staticfn void
1467 nsb_mung_line(p)
1468 char *p;
1470 while ((p = strchr(p, ' ')) != 0)
1471 *p = '|';
1474 staticfn void
1475 nsb_unmung_line(p)
1476 char *p;
1478 while ((p = strchr(p, '|')) != 0)
1479 *p = ' ';
1481 #endif /* NO_SCAN_BRACK */
1483 /*topten.c*/