OSDN Git Service

shrink mine
[nethackexpress/trunk.git] / src / topten.c
1 /*      SCCS Id: @(#)topten.c   3.4     2000/01/21      */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 #include "hack.h"
6 #include "dlb.h"
7 #ifdef SHORT_FILENAMES
8 #include "patchlev.h"
9 #else
10 #include "patchlevel.h"
11 #endif
12
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
18
19 /*
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.
24  */
25 #ifdef UPDATE_RECORD_IN_PLACE
26 static long final_fpos;
27 #endif
28
29 #define done_stopprint program_state.stopprint
30
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
36 #define PERSMAX  3              /* entries per name/uid per char. allowed */
37 #define POINTSMIN       1       /* must be > 0 */
38 #define ENTRYMAX        100     /* must be >= 10 */
39
40 #if !defined(MICRO) && !defined(MAC) && !defined(WIN32)
41 #define PERS_IS_UID             /* delete for PERSMAX per name; now per uid */
42 #endif
43 struct toptenentry {
44         struct toptenentry *tt_next;
45 #ifdef UPDATE_RECORD_IN_PLACE
46         long fpos;
47 #endif
48         long points;
49         int deathdnum, deathlev;
50         int maxlvl, hp, maxhp, deaths;
51         int ver_major, ver_minor, patchlevel;
52         long deathdate, birthdate;
53         int uid;
54         char plrole[ROLESZ+1];
55         char plrace[ROLESZ+1];
56         char plgend[ROLESZ+1];
57         char plalign[ROLESZ+1];
58         char name[NAMSZ+1];
59         char death[DTHSZ+1];
60 } *tt_head;
61
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(readentry, (FILE *,struct toptenentry *));
68 STATIC_DCL void FDECL(writeentry, (FILE *,struct toptenentry *));
69 STATIC_DCL void FDECL(free_ttlist, (struct toptenentry *));
70 STATIC_DCL int FDECL(classmon, (char *,BOOLEAN_P));
71 STATIC_DCL int FDECL(score_wanted,
72                 (BOOLEAN_P, int,struct toptenentry *,int,const char **,int));
73 #ifdef NO_SCAN_BRACK
74 STATIC_DCL void FDECL(nsb_mung_line,(char*));
75 STATIC_DCL void FDECL(nsb_unmung_line,(char*));
76 #endif
77
78 /* must fit with end.c; used in rip.c */
79 NEARDATA const char * const killed_by_prefix[] = {
80         "killed by ", "choked on ", "poisoned by ", "died of ", "drowned in ",
81         "burned by ", "dissolved in ", "crushed to death by ", "petrified by ",
82         "turned to slime by ", "killed by ", "", "", "", "", ""
83 };
84
85 static winid toptenwin = WIN_ERR;
86
87 STATIC_OVL void
88 topten_print(x)
89 const char *x;
90 {
91         if (toptenwin == WIN_ERR)
92             raw_print(x);
93         else
94             putstr(toptenwin, ATR_NONE, x);
95 }
96
97 STATIC_OVL void
98 topten_print_bold(x)
99 const char *x;
100 {
101         if (toptenwin == WIN_ERR)
102             raw_print_bold(x);
103         else
104             putstr(toptenwin, ATR_BOLD, x);
105 }
106
107 STATIC_OVL xchar
108 observable_depth(lev)
109 d_level *lev;
110 {
111 #if 0   /* if we ever randomize the order of the elemental planes, we
112            must use a constant external representation in the record file */
113         if (In_endgame(lev)) {
114             if (Is_astralevel(lev))      return -5;
115             else if (Is_waterlevel(lev)) return -4;
116             else if (Is_firelevel(lev))  return -3;
117             else if (Is_airlevel(lev))   return -2;
118             else if (Is_earthlevel(lev)) return -1;
119             else                         return 0;      /* ? */
120         } else
121 #endif
122             return depth(lev);
123 }
124
125 STATIC_OVL void
126 readentry(rfile,tt)
127 FILE *rfile;
128 struct toptenentry *tt;
129 {
130 #ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
131         static const char fmt[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c";
132         static const char fmt32[] = "%c%c %s %s%*c";
133         static const char fmt33[] = "%s %s %s %s %s %s%*c";
134 #else
135         static const char fmt[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
136         static const char fmt32[] = "%c%c %[^,],%[^\n]%*c";
137         static const char fmt33[] = "%s %s %s %s %[^,],%[^\n]%*c";
138 #endif
139
140 #ifdef UPDATE_RECORD_IN_PLACE
141         /* note: fscanf() below must read the record's terminating newline */
142         final_fpos = tt->fpos = ftell(rfile);
143 #endif
144 #define TTFIELDS 13
145         if(fscanf(rfile, fmt,
146                         &tt->ver_major, &tt->ver_minor, &tt->patchlevel,
147                         &tt->points, &tt->deathdnum, &tt->deathlev,
148                         &tt->maxlvl, &tt->hp, &tt->maxhp, &tt->deaths,
149                         &tt->deathdate, &tt->birthdate,
150                         &tt->uid) != TTFIELDS)
151 #undef TTFIELDS
152                 tt->points = 0;
153         else {
154                 /* Check for backwards compatibility */
155                 if (tt->ver_major < 3 ||
156                                 (tt->ver_major == 3 && tt->ver_minor < 3)) {
157                         int i;
158
159                     if (fscanf(rfile, fmt32,
160                                 tt->plrole, tt->plgend,
161                                 tt->name, tt->death) != 4)
162                         tt->points = 0;
163                     tt->plrole[1] = '\0';
164                     if ((i = str2role(tt->plrole)) >= 0)
165                         Strcpy(tt->plrole, roles[i].filecode);
166                     Strcpy(tt->plrace, "?");
167                     Strcpy(tt->plgend, (tt->plgend[0] == 'M') ? "Mal" : "Fem");
168                     Strcpy(tt->plalign, "?");
169                 } else if (fscanf(rfile, fmt33,
170                                 tt->plrole, tt->plrace, tt->plgend,
171                                 tt->plalign, tt->name, tt->death) != 6)
172                         tt->points = 0;
173 #ifdef NO_SCAN_BRACK
174                 if(tt->points > 0) {
175                         nsb_unmung_line(tt->name);
176                         nsb_unmung_line(tt->death);
177                 }
178 #endif
179         }
180
181         /* check old score entries for Y2K problem and fix whenever found */
182         if (tt->points > 0) {
183                 if (tt->birthdate < 19000000L) tt->birthdate += 19000000L;
184                 if (tt->deathdate < 19000000L) tt->deathdate += 19000000L;
185         }
186 }
187
188 STATIC_OVL void
189 writeentry(rfile,tt)
190 FILE *rfile;
191 struct toptenentry *tt;
192 {
193 #ifdef NO_SCAN_BRACK
194         nsb_mung_line(tt->name);
195         nsb_mung_line(tt->death);
196                            /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
197         (void) fprintf(rfile,"%d %d %d %ld %d %d %d %d %d %d %ld %ld %d ",
198 #else
199         (void) fprintf(rfile,"%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ",
200 #endif
201                 tt->ver_major, tt->ver_minor, tt->patchlevel,
202                 tt->points, tt->deathdnum, tt->deathlev,
203                 tt->maxlvl, tt->hp, tt->maxhp, tt->deaths,
204                 tt->deathdate, tt->birthdate, tt->uid);
205         if (tt->ver_major < 3 ||
206                         (tt->ver_major == 3 && tt->ver_minor < 3))
207 #ifdef NO_SCAN_BRACK
208                 (void) fprintf(rfile,"%c%c %s %s\n",
209 #else
210                 (void) fprintf(rfile,"%c%c %s,%s\n",
211 #endif
212                         tt->plrole[0], tt->plgend[0],
213                         onlyspace(tt->name) ? "_" : tt->name, tt->death);
214         else
215 #ifdef NO_SCAN_BRACK
216                 (void) fprintf(rfile,"%s %s %s %s %s %s\n",
217 #else
218                 (void) fprintf(rfile,"%s %s %s %s %s,%s\n",
219 #endif
220                         tt->plrole, tt->plrace, tt->plgend, tt->plalign,
221                         onlyspace(tt->name) ? "_" : tt->name, tt->death);
222
223 #ifdef NO_SCAN_BRACK
224         nsb_unmung_line(tt->name);
225         nsb_unmung_line(tt->death);
226 #endif
227 }
228
229 STATIC_OVL void
230 free_ttlist(tt)
231 struct toptenentry *tt;
232 {
233         struct toptenentry *ttnext;
234
235         while (tt->points > 0) {
236                 ttnext = tt->tt_next;
237                 dealloc_ttentry(tt);
238                 tt = ttnext;
239         }
240         dealloc_ttentry(tt);
241 }
242
243 void
244 topten(how)
245 int how;
246 {
247         int uid = getuid();
248         int rank, rank0 = -1, rank1 = 0;
249         int occ_cnt = PERSMAX;
250         register struct toptenentry *t0, *tprev;
251         struct toptenentry *t1;
252         FILE *rfile;
253         register int flg = 0;
254         boolean t0_used;
255 #ifdef LOGFILE
256         FILE *lfile;
257 #endif /* LOGFILE */
258
259 /* Under DICE 3.0, this crashes the system consistently, apparently due to
260  * corruption of *rfile somewhere.  Until I figure this out, just cut out
261  * topten support entirely - at least then the game exits cleanly.  --AC
262  */
263 #ifdef _DCC
264         return;
265 #endif
266
267 /* If we are in the midst of a panic, cut out topten entirely.
268  * topten uses alloc() several times, which will lead to
269  * problems if the panic was the result of an alloc() failure.
270  */
271         if (program_state.panicking)
272                 return;
273
274         if (flags.toptenwin) {
275             toptenwin = create_nhwindow(NHW_TEXT);
276         }
277
278 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
279 #define HUP     if (!program_state.done_hup)
280 #else
281 #define HUP
282 #endif
283
284 #ifdef TOS
285         restore_colors();       /* make sure the screen is black on white */
286 #endif
287         /* create a new 'topten' entry */
288         t0_used = FALSE;
289         t0 = newttentry();
290         /* deepest_lev_reached() is in terms of depth(), and reporting the
291          * deepest level reached in the dungeon death occurred in doesn't
292          * seem right, so we have to report the death level in depth() terms
293          * as well (which also seems reasonable since that's all the player
294          * sees on the screen anyway)
295          */
296         t0->ver_major = VERSION_MAJOR;
297         t0->ver_minor = VERSION_MINOR;
298         t0->patchlevel = PATCHLEVEL;
299         t0->points = u.urexp;
300         t0->deathdnum = u.uz.dnum;
301         t0->deathlev = observable_depth(&u.uz);
302         t0->maxlvl = deepest_lev_reached(TRUE);
303         t0->hp = u.uhp;
304         t0->maxhp = u.uhpmax;
305         t0->deaths = u.umortality;
306         t0->uid = uid;
307         (void) strncpy(t0->plrole, urole.filecode, ROLESZ);
308         t0->plrole[ROLESZ] = '\0';
309         (void) strncpy(t0->plrace, urace.filecode, ROLESZ);
310         t0->plrace[ROLESZ] = '\0';
311         (void) strncpy(t0->plgend, genders[flags.female].filecode, ROLESZ);
312         t0->plgend[ROLESZ] = '\0';
313         (void) strncpy(t0->plalign, aligns[1-u.ualign.type].filecode, ROLESZ);
314         t0->plalign[ROLESZ] = '\0';
315         (void) strncpy(t0->name, plname, NAMSZ);
316         t0->name[NAMSZ] = '\0';
317         t0->death[0] = '\0';
318         switch (killer_format) {
319                 default: impossible("bad killer format?");
320                 case KILLED_BY_AN:
321                         Strcat(t0->death, killed_by_prefix[how]);
322                         (void) strncat(t0->death, an(killer),
323                                                 DTHSZ-strlen(t0->death));
324                         break;
325                 case KILLED_BY:
326                         Strcat(t0->death, killed_by_prefix[how]);
327                         (void) strncat(t0->death, killer,
328                                                 DTHSZ-strlen(t0->death));
329                         break;
330                 case NO_KILLER_PREFIX:
331                         (void) strncat(t0->death, killer, DTHSZ);
332                         break;
333         }
334         t0->birthdate = yyyymmdd(u.ubirthday);
335         t0->deathdate = yyyymmdd((time_t)0L);
336         t0->tt_next = 0;
337 #ifdef UPDATE_RECORD_IN_PLACE
338         t0->fpos = -1L;
339 #endif
340
341 #ifdef LOGFILE          /* used for debugging (who dies of what, where) */
342         if (lock_file(LOGFILE, SCOREPREFIX, 10)) {
343             if(!(lfile = fopen_datafile(LOGFILE, "a", SCOREPREFIX))) {
344                 HUP raw_print("Cannot open log file!");
345             } else {
346                 writeentry(lfile, t0);
347                 (void) fclose(lfile);
348             }
349             unlock_file(LOGFILE);
350         }
351 #endif /* LOGFILE */
352
353         if (wizard || discover) {
354             if (how != PANICKED) HUP {
355                 char pbuf[BUFSZ];
356                 topten_print("");
357                 Sprintf(pbuf,
358               "Since you were in %s mode, the score list will not be checked.",
359                     wizard ? "wizard" : "discover");
360                 topten_print(pbuf);
361             }
362             goto showwin;
363         }
364
365         if (!lock_file(RECORD, SCOREPREFIX, 60))
366                 goto destroywin;
367
368 #ifdef UPDATE_RECORD_IN_PLACE
369         rfile = fopen_datafile(RECORD, "r+", SCOREPREFIX);
370 #else
371         rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
372 #endif
373
374         if (!rfile) {
375                 HUP raw_print("Cannot open record file!");
376                 unlock_file(RECORD);
377                 goto destroywin;
378         }
379
380         HUP topten_print("");
381
382         /* assure minimum number of points */
383         if(t0->points < POINTSMIN) t0->points = 0;
384
385         t1 = tt_head = newttentry();
386         tprev = 0;
387         /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
388         for(rank = 1; ; ) {
389             readentry(rfile, t1);
390             if (t1->points < POINTSMIN) t1->points = 0;
391             if(rank0 < 0 && t1->points < t0->points) {
392                 rank0 = rank++;
393                 if(tprev == 0)
394                         tt_head = t0;
395                 else
396                         tprev->tt_next = t0;
397                 t0->tt_next = t1;
398 #ifdef UPDATE_RECORD_IN_PLACE
399                 t0->fpos = t1->fpos;    /* insert here */
400 #endif
401                 t0_used = TRUE;
402                 occ_cnt--;
403                 flg++;          /* ask for a rewrite */
404             } else tprev = t1;
405
406             if(t1->points == 0) break;
407             if(
408 #ifdef PERS_IS_UID
409                 t1->uid == t0->uid &&
410 #else
411                 strncmp(t1->name, t0->name, NAMSZ) == 0 &&
412 #endif
413                 !strncmp(t1->plrole, t0->plrole, ROLESZ) &&
414                 --occ_cnt <= 0) {
415                     if(rank0 < 0) {
416                         rank0 = 0;
417                         rank1 = rank;
418                         HUP {
419                             char pbuf[BUFSZ];
420                             Sprintf(pbuf,
421                           "You didn't beat your previous score of %ld points.",
422                                     t1->points);
423                             topten_print(pbuf);
424                             topten_print("");
425                         }
426                     }
427                     if(occ_cnt < 0) {
428                         flg++;
429                         continue;
430                     }
431                 }
432             if(rank <= ENTRYMAX) {
433                 t1->tt_next = newttentry();
434                 t1 = t1->tt_next;
435                 rank++;
436             }
437             if(rank > ENTRYMAX) {
438                 t1->points = 0;
439                 break;
440             }
441         }
442         if(flg) {       /* rewrite record file */
443 #ifdef UPDATE_RECORD_IN_PLACE
444                 (void) fseek(rfile, (t0->fpos >= 0 ?
445                                      t0->fpos : final_fpos), SEEK_SET);
446 #else
447                 (void) fclose(rfile);
448                 if(!(rfile = fopen_datafile(RECORD, "w", SCOREPREFIX))){
449                         HUP raw_print("Cannot write record file");
450                         unlock_file(RECORD);
451                         free_ttlist(tt_head);
452                         goto destroywin;
453                 }
454 #endif  /* UPDATE_RECORD_IN_PLACE */
455                 if(!done_stopprint) if(rank0 > 0){
456                     if(rank0 <= 10)
457                         topten_print("You made the top ten list!");
458                     else {
459                         char pbuf[BUFSZ];
460                         Sprintf(pbuf,
461                           "You reached the %d%s place on the top %d list.",
462                                 rank0, ordin(rank0), ENTRYMAX);
463                         topten_print(pbuf);
464                     }
465                     topten_print("");
466                 }
467         }
468         if(rank0 == 0) rank0 = rank1;
469         if(rank0 <= 0) rank0 = rank;
470         if(!done_stopprint) outheader();
471         t1 = tt_head;
472         for(rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
473             if(flg
474 #ifdef UPDATE_RECORD_IN_PLACE
475                     && rank >= rank0
476 #endif
477                 ) writeentry(rfile, t1);
478             if (done_stopprint) continue;
479             if (rank > flags.end_top &&
480                     (rank < rank0 - flags.end_around ||
481                      rank > rank0 + flags.end_around) &&
482                     (!flags.end_own ||
483 #ifdef PERS_IS_UID
484                                         t1->uid != t0->uid
485 #else
486                                         strncmp(t1->name, t0->name, NAMSZ)
487 #endif
488                 )) continue;
489             if (rank == rank0 - flags.end_around &&
490                     rank0 > flags.end_top + flags.end_around + 1 &&
491                     !flags.end_own)
492                 topten_print("");
493             if(rank != rank0)
494                 outentry(rank, t1, FALSE);
495             else if(!rank1)
496                 outentry(rank, t1, TRUE);
497             else {
498                 outentry(rank, t1, TRUE);
499                 outentry(0, t0, TRUE);
500             }
501         }
502         if(rank0 >= rank) if(!done_stopprint)
503                 outentry(0, t0, TRUE);
504 #ifdef UPDATE_RECORD_IN_PLACE
505         if (flg) {
506 # ifdef TRUNCATE_FILE
507             /* if a reasonable way to truncate a file exists, use it */
508             truncate_file(rfile);
509 # else
510             /* use sentinel record rather than relying on truncation */
511             t1->points = 0L;    /* terminates file when read back in */
512             t1->ver_major = t1->ver_minor = t1->patchlevel = 0;
513             t1->uid = t1->deathdnum = t1->deathlev = 0;
514             t1->maxlvl = t1->hp = t1->maxhp = t1->deaths = 0;
515             t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-';
516             t1->plrole[1] = t1->plrace[1] = t1->plgend[1] = t1->plalign[1] = 0;
517             t1->birthdate = t1->deathdate = yyyymmdd((time_t)0L);
518             Strcpy(t1->name, "@");
519             Strcpy(t1->death, "<eod>\n");
520             writeentry(rfile, t1);
521             (void) fflush(rfile);
522 # endif /* TRUNCATE_FILE */
523         }
524 #endif  /* UPDATE_RECORD_IN_PLACE */
525         (void) fclose(rfile);
526         unlock_file(RECORD);
527         free_ttlist(tt_head);
528
529   showwin:
530         if (flags.toptenwin && !done_stopprint) display_nhwindow(toptenwin, 1);
531   destroywin:
532         if (!t0_used) dealloc_ttentry(t0);
533         if (flags.toptenwin) {
534             destroy_nhwindow(toptenwin);
535             toptenwin=WIN_ERR;
536         }
537 }
538
539 STATIC_OVL void
540 outheader()
541 {
542         char linebuf[BUFSZ];
543         register char *bp;
544
545         Strcpy(linebuf, " No  Points     Name");
546         bp = eos(linebuf);
547         while(bp < linebuf + COLNO - 9) *bp++ = ' ';
548         Strcpy(bp, "Hp [max]");
549         topten_print(linebuf);
550 }
551
552 /* so>0: standout line; so=0: ordinary line */
553 STATIC_OVL void
554 outentry(rank, t1, so)
555 struct toptenentry *t1;
556 int rank;
557 boolean so;
558 {
559         boolean second_line = TRUE;
560         char linebuf[BUFSZ];
561         char *bp, hpbuf[24], linebuf3[BUFSZ];
562         int hppos, lngr;
563
564
565         linebuf[0] = '\0';
566         if (rank) Sprintf(eos(linebuf), "%3d", rank);
567         else Strcat(linebuf, "   ");
568
569         Sprintf(eos(linebuf), " %10ld  %.10s", t1->points, t1->name);
570         Sprintf(eos(linebuf), "-%s", t1->plrole);
571         if (t1->plrace[0] != '?')
572                 Sprintf(eos(linebuf), "-%s", t1->plrace);
573         /* Printing of gender and alignment is intentional.  It has been
574          * part of the NetHack Geek Code, and illustrates a proper way to
575          * specify a character from the command line.
576          */
577         Sprintf(eos(linebuf), "-%s", t1->plgend);
578         if (t1->plalign[0] != '?')
579                 Sprintf(eos(linebuf), "-%s ", t1->plalign);
580         else
581                 Strcat(linebuf, " ");
582         if (!strncmp("escaped", t1->death, 7)) {
583             Sprintf(eos(linebuf), "escaped the dungeon %s[max level %d]",
584                     !strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "",
585                     t1->maxlvl);
586             /* fixup for closing paren in "escaped... with...Amulet)[max..." */
587             if ((bp = index(linebuf, ')')) != 0)
588                 *bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' ';
589             second_line = FALSE;
590         } else if (!strncmp("ascended", t1->death, 8)) {
591             Sprintf(eos(linebuf), "ascended to demigod%s-hood",
592                     (t1->plgend[0] == 'F') ? "dess" : "");
593             second_line = FALSE;
594         } else {
595             if (!strncmp(t1->death, "quit", 4)) {
596                 Strcat(linebuf, "quit");
597                 second_line = FALSE;
598             } else if (!strncmp(t1->death, "died of st", 10)) {
599                 Strcat(linebuf, "starved to death");
600                 second_line = FALSE;
601             } else if (!strncmp(t1->death, "choked", 6)) {
602                 Sprintf(eos(linebuf), "choked on h%s food",
603                         (t1->plgend[0] == 'F') ? "er" : "is");
604             } else if (!strncmp(t1->death, "poisoned", 8)) {
605                 Strcat(linebuf, "was poisoned");
606             } else if (!strncmp(t1->death, "crushed", 7)) {
607                 Strcat(linebuf, "was crushed to death");
608             } else if (!strncmp(t1->death, "petrified by ", 13)) {
609                 Strcat(linebuf, "turned to stone");
610             } else Strcat(linebuf, "died");
611
612             if (t1->deathdnum == astral_level.dnum) {
613                 const char *arg, *fmt = " on the Plane of %s";
614
615                 switch (t1->deathlev) {
616                 case -5:
617                         fmt = " on the %s Plane";
618                         arg = "Astral"; break;
619                 case -4:
620                         arg = "Water";  break;
621                 case -3:
622                         arg = "Fire";   break;
623                 case -2:
624                         arg = "Air";    break;
625                 case -1:
626                         arg = "Earth";  break;
627                 default:
628                         arg = "Void";   break;
629                 }
630                 Sprintf(eos(linebuf), fmt, arg);
631             } else {
632                 Sprintf(eos(linebuf), " in %s", dungeons[t1->deathdnum].dname);
633                 if (t1->deathdnum != knox_level.dnum)
634                     Sprintf(eos(linebuf), " on level %d", t1->deathlev);
635                 if (t1->deathlev != t1->maxlvl)
636                     Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
637             }
638
639             /* kludge for "quit while already on Charon's boat" */
640             if (!strncmp(t1->death, "quit ", 5))
641                 Strcat(linebuf, t1->death + 4);
642         }
643         Strcat(linebuf, ".");
644
645         /* Quit, starved, ascended, and escaped contain no second line */
646         if (second_line)
647             Sprintf(eos(linebuf), "  %c%s.", highc(*(t1->death)), t1->death+1);
648
649         lngr = (int)strlen(linebuf);
650         if (t1->hp <= 0) hpbuf[0] = '-', hpbuf[1] = '\0';
651         else Sprintf(hpbuf, "%d", t1->hp);
652         /* beginning of hp column after padding (not actually padded yet) */
653         hppos = COLNO - (sizeof("  Hp [max]")-1); /* sizeof(str) includes \0 */
654         while (lngr >= hppos) {
655             for(bp = eos(linebuf);
656                     !(*bp == ' ' && (bp-linebuf < hppos));
657                     bp--)
658                 ;
659             /* special case: if about to wrap in the middle of maximum
660                dungeon depth reached, wrap in front of it instead */
661             if (bp > linebuf + 5 && !strncmp(bp - 5, " [max", 5)) bp -= 5;
662             Strcpy(linebuf3, bp+1);
663             *bp = 0;
664             if (so) {
665                 while (bp < linebuf + (COLNO-1)) *bp++ = ' ';
666                 *bp = 0;
667                 topten_print_bold(linebuf);
668             } else
669                 topten_print(linebuf);
670             Sprintf(linebuf, "%15s %s", "", linebuf3);
671             lngr = strlen(linebuf);
672         }
673         /* beginning of hp column not including padding */
674         hppos = COLNO - 7 - (int)strlen(hpbuf);
675         bp = eos(linebuf);
676
677         if (bp <= linebuf + hppos) {
678             /* pad any necessary blanks to the hit point entry */
679             while (bp < linebuf + hppos) *bp++ = ' ';
680             Strcpy(bp, hpbuf);
681             Sprintf(eos(bp), " %s[%d]",
682                     (t1->maxhp < 10) ? "  " : (t1->maxhp < 100) ? " " : "",
683                     t1->maxhp);
684         }
685
686         if (so) {
687             bp = eos(linebuf);
688             if (so >= COLNO) so = COLNO-1;
689             while (bp < linebuf + so) *bp++ = ' ';
690             *bp = 0;
691             topten_print_bold(linebuf);
692         } else
693             topten_print(linebuf);
694 }
695
696 STATIC_OVL int
697 score_wanted(current_ver, rank, t1, playerct, players, uid)
698 boolean current_ver;
699 int rank;
700 struct toptenentry *t1;
701 int playerct;
702 const char **players;
703 int uid;
704 {
705         int i;
706
707         if (current_ver && (t1->ver_major != VERSION_MAJOR ||
708                             t1->ver_minor != VERSION_MINOR ||
709                             t1->patchlevel != PATCHLEVEL))
710                 return 0;
711
712 #ifdef PERS_IS_UID
713         if (!playerct && t1->uid == uid)
714                 return 1;
715 #endif
716
717         for (i = 0; i < playerct; i++) {
718             if (players[i][0] == '-' && index("pr", players[i][1]) &&
719                 players[i][2] == 0 && i + 1 < playerct) {
720                 char *arg = (char *)players[i + 1];
721                 if ((players[i][1] == 'p' &&
722                      str2role(arg) == str2role(t1->plrole)) ||
723                     (players[i][1] == 'r' &&
724                      str2race(arg) == str2race(t1->plrace)))
725                     return 1;
726                 i++;
727             } else if (strcmp(players[i], "all") == 0 ||
728                     strncmp(t1->name, players[i], NAMSZ) == 0 ||
729                     (players[i][0] == '-' &&
730                      players[i][1] == t1->plrole[0] &&
731                      players[i][2] == 0) ||
732                     (digit(players[i][0]) && rank <= atoi(players[i])))
733                 return 1;
734         }
735         return 0;
736 }
737
738 /*
739  * print selected parts of score list.
740  * argc >= 2, with argv[0] untrustworthy (directory names, et al.),
741  * and argv[1] starting with "-s".
742  */
743 void
744 prscore(argc,argv)
745 int argc;
746 char **argv;
747 {
748         const char **players;
749         int playerct, rank;
750         boolean current_ver = TRUE, init_done = FALSE;
751         register struct toptenentry *t1;
752         FILE *rfile;
753         boolean match_found = FALSE;
754         register int i;
755         char pbuf[BUFSZ];
756         int uid = -1;
757 #ifndef PERS_IS_UID
758         const char *player0;
759 #endif
760
761         if (argc < 2 || strncmp(argv[1], "-s", 2)) {
762                 raw_printf("prscore: bad arguments (%d)", argc);
763                 return;
764         }
765
766         rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
767         if (!rfile) {
768                 raw_print("Cannot open record file!");
769                 return;
770         }
771
772 #ifdef  AMIGA
773         {
774             extern winid amii_rawprwin;
775             init_nhwindows(&argc, argv);
776             amii_rawprwin = create_nhwindow(NHW_TEXT);
777         }
778 #endif
779
780         /* If the score list isn't after a game, we never went through
781          * initialization. */
782         if (wiz1_level.dlevel == 0) {
783                 dlb_init();
784                 init_dungeons();
785                 init_done = TRUE;
786         }
787
788         if (!argv[1][2]){       /* plain "-s" */
789                 argc--;
790                 argv++;
791         } else  argv[1] += 2;
792
793         if (argc > 1 && !strcmp(argv[1], "-v")) {
794                 current_ver = FALSE;
795                 argc--;
796                 argv++;
797         }
798
799         if (argc <= 1) {
800 #ifdef PERS_IS_UID
801                 uid = getuid();
802                 playerct = 0;
803                 players = (const char **)0;
804 #else
805                 player0 = plname;
806                 if (!*player0)
807 # ifdef AMIGA
808                         player0 = "all";        /* single user system */
809 # else
810                         player0 = "hackplayer";
811 # endif
812                 playerct = 1;
813                 players = &player0;
814 #endif
815         } else {
816                 playerct = --argc;
817                 players = (const char **)++argv;
818         }
819         raw_print("");
820
821         t1 = tt_head = newttentry();
822         for (rank = 1; ; rank++) {
823             readentry(rfile, t1);
824             if (t1->points == 0) break;
825             if (!match_found &&
826                     score_wanted(current_ver, rank, t1, playerct, players, uid))
827                 match_found = TRUE;
828             t1->tt_next = newttentry();
829             t1 = t1->tt_next;
830         }
831
832         (void) fclose(rfile);
833         if (init_done) {
834             free_dungeons();
835             dlb_cleanup();
836         }
837
838         if (match_found) {
839             outheader();
840             t1 = tt_head;
841             for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
842                 if (score_wanted(current_ver, rank, t1, playerct, players, uid))
843                     (void) outentry(rank, t1, 0);
844             }
845         } else {
846             Sprintf(pbuf, "Cannot find any %sentries for ",
847                                 current_ver ? "current " : "");
848             if (playerct < 1) Strcat(pbuf, "you.");
849             else {
850                 if (playerct > 1) Strcat(pbuf, "any of ");
851                 for (i = 0; i < playerct; i++) {
852                     /* stop printing players if there are too many to fit */
853                     if (strlen(pbuf) + strlen(players[i]) + 2 >= BUFSZ) {
854                         if (strlen(pbuf) < BUFSZ-4) Strcat(pbuf, "...");
855                         else Strcpy(pbuf+strlen(pbuf)-4, "...");
856                         break;
857                     }
858                     Strcat(pbuf, players[i]);
859                     if (i < playerct-1) {
860                         if (players[i][0] == '-' &&
861                             index("pr", players[i][1]) && players[i][2] == 0)
862                             Strcat(pbuf, " ");
863                         else Strcat(pbuf, ":");
864                     }
865                 }
866             }
867             raw_print(pbuf);
868             raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
869
870                          hname);
871             raw_printf("Player types are: [-p role] [-r race]");
872         }
873         free_ttlist(tt_head);
874 #ifdef  AMIGA
875         {
876             extern winid amii_rawprwin;
877             display_nhwindow(amii_rawprwin, 1);
878             destroy_nhwindow(amii_rawprwin);
879             amii_rawprwin = WIN_ERR;
880         }
881 #endif
882 }
883
884 STATIC_OVL int
885 classmon(plch, fem)
886         char *plch;
887         boolean fem;
888 {
889         int i;
890
891         /* Look for this role in the role table */
892         for (i = 0; roles[i].name.m; i++)
893             if (!strncmp(plch, roles[i].filecode, ROLESZ)) {
894                 if (fem && roles[i].femalenum != NON_PM)
895                     return roles[i].femalenum;
896                 else if (roles[i].malenum != NON_PM)
897                     return roles[i].malenum;
898                 else
899                     return PM_HUMAN;
900             }
901         /* this might be from a 3.2.x score for former Elf class */
902         if (!strcmp(plch, "E")) return PM_RANGER;
903
904         impossible("What weird role is this? (%s)", plch);
905         return (PM_HUMAN_MUMMY);
906 }
907
908 /*
909  * Get a random player name and class from the high score list,
910  * and attach them to an object (for statues or morgue corpses).
911  */
912 struct obj *
913 tt_oname(otmp)
914 struct obj *otmp;
915 {
916         int rank;
917         register int i;
918         register struct toptenentry *tt;
919         FILE *rfile;
920         struct toptenentry tt_buf;
921
922         if (!otmp) return((struct obj *) 0);
923
924         rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
925         if (!rfile) {
926                 impossible("Cannot open record file!");
927                 return (struct obj *)0;
928         }
929
930         tt = &tt_buf;
931         rank = rnd(10);
932 pickentry:
933         for(i = rank; i; i--) {
934             readentry(rfile, tt);
935             if(tt->points == 0) break;
936         }
937
938         if(tt->points == 0) {
939                 if(rank > 1) {
940                         rank = 1;
941                         rewind(rfile);
942                         goto pickentry;
943                 }
944                 otmp = (struct obj *) 0;
945         } else {
946                 /* reset timer in case corpse started out as lizard or troll */
947                 if (otmp->otyp == CORPSE) obj_stop_timers(otmp);
948                 otmp->corpsenm = classmon(tt->plrole, (tt->plgend[0] == 'F'));
949                 otmp->owt = weight(otmp);
950                 otmp = oname(otmp, tt->name);
951                 if (otmp->otyp == CORPSE) start_corpse_timeout(otmp);
952         }
953
954         (void) fclose(rfile);
955         return otmp;
956 }
957
958 #ifdef NO_SCAN_BRACK
959 /* Lattice scanf isn't up to reading the scorefile.  What */
960 /* follows deals with that; I admit it's ugly. (KL) */
961 /* Now generally available (KL) */
962 STATIC_OVL void
963 nsb_mung_line(p)
964         char *p;
965 {
966         while ((p = index(p, ' ')) != 0) *p = '|';
967 }
968
969 STATIC_OVL void
970 nsb_unmung_line(p)
971         char *p;
972 {
973         while ((p = index(p, '|')) != 0) *p = ' ';
974 }
975 #endif /* NO_SCAN_BRACK */
976
977 /*topten.c*/