1 /* NetHack 3.6 topten.c $NHDT-Date: 1450451497 2015/12/18 15:11:37 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.44 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
10 #include "patchlevel.h"
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
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
;
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))
38 struct toptenentry
*tt_next
;
39 #ifdef UPDATE_RECORD_IN_PLACE
43 int deathdnum
, deathlev
;
44 int maxlvl
, hp
, maxhp
, deaths
;
45 int ver_major
, ver_minor
, patchlevel
;
46 long deathdate
, birthdate
;
48 char plrole
[ROLESZ
+ 1];
49 char plrace
[ROLESZ
+ 1];
50 char plgend
[ROLESZ
+ 1];
51 char plalign
[ROLESZ
+ 1];
53 char death
[DTHSZ
+ 1];
55 /* size big enough to read in all the string fields at once; includes
56 room for separating space or trailing newline plus string terminator */
57 #define SCANBUFSZ (4 * (ROLESZ + 1) + (NAMSZ + 1) + (DTHSZ + 1) + 1)
59 STATIC_DCL
void FDECL(topten_print
, (const char *));
60 STATIC_DCL
void FDECL(topten_print_bold
, (const char *));
61 STATIC_DCL xchar
FDECL(observable_depth
, (d_level
*));
62 STATIC_DCL
void NDECL(outheader
);
63 STATIC_DCL
void FDECL(outentry
, (int, struct toptenentry
*, BOOLEAN_P
));
64 STATIC_DCL
void FDECL(discardexcess
, (FILE *));
65 STATIC_DCL
void FDECL(readentry
, (FILE *, struct toptenentry
*));
66 STATIC_DCL
void FDECL(writeentry
, (FILE *, struct toptenentry
*));
68 STATIC_DCL
void FDECL(writexlentry
, (FILE *, struct toptenentry
*, int));
69 STATIC_DCL
long NDECL(encodexlogflags
);
70 STATIC_DCL
long NDECL(encodeconduct
);
71 STATIC_DCL
long NDECL(encodeachieve
);
73 STATIC_DCL
void FDECL(free_ttlist
, (struct toptenentry
*));
74 STATIC_DCL
int FDECL(classmon
, (char *, BOOLEAN_P
));
75 STATIC_DCL
int FDECL(score_wanted
, (BOOLEAN_P
, int, struct toptenentry
*, int,
78 STATIC_DCL
void FDECL(nsb_mung_line
, (char *));
79 STATIC_DCL
void FDECL(nsb_unmung_line
, (char *));
82 static winid toptenwin
= WIN_ERR
;
84 /* "killed by",&c ["an"] 'killer.name' */
86 formatkiller(buf
, siz
, how
, incl_helpless
)
90 boolean incl_helpless
;
92 static NEARDATA
const char *const killed_by_prefix
[] = {
93 /* DIED, CHOKING, POISONING, STARVING, */
94 "killed by ", "choked on ", "poisoned by ", "died of ",
95 /* DROWNING, BURNING, DISSOLVED, CRUSHING, */
96 "drowned in ", "burned by ", "dissolved in ", "crushed to death by ",
97 /* STONING, TURNED_SLIME, GENOCIDED, */
98 "petrified by ", "turned to slime by ", "killed by ",
99 /* PANICKED, TRICKED, QUIT, ESCAPED, ASCENDED */
103 char c
, *kname
= killer
.name
;
105 buf
[0] = '\0'; /* lint suppression */
106 switch (killer
.format
) {
108 impossible("bad killer format? (%d)", killer
.format
);
110 case NO_KILLER_PREFIX
:
116 (void) strncat(buf
, killed_by_prefix
[how
], siz
- 1);
121 /* Copy kname into buf[].
122 * Object names and named fruit have already been sanitized, but
123 * monsters can have "called 'arbitrary text'" attached to them,
124 * so make sure that that text can't confuse field splitting when
125 * record, logfile, or xlogfile is re-read at some later point.
131 /* 'xlogfile' doesn't really need protection for '=', but
132 fixrecord.awk for corrupted 3.6.0 'record' does (only
133 if using xlogfile rather than logfile to repair record) */
136 /* tab is not possible due to use of mungspaces() when naming;
137 it would disrupt xlogfile parsing if it were present */
144 if (incl_helpless
&& multi
) {
145 /* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */
146 if (multi_reason
&& strlen(multi_reason
) + sizeof ", while " <= siz
)
147 Sprintf(buf
, ", while %s", multi_reason
);
148 /* either multi_reason wasn't specified or wouldn't fit */
149 else if (sizeof ", while helpless" <= siz
)
150 Strcpy(buf
, ", while helpless");
151 /* else extra death info won't fit, so leave it out */
159 if (toptenwin
== WIN_ERR
)
162 putstr(toptenwin
, ATR_NONE
, x
);
169 if (toptenwin
== WIN_ERR
)
172 putstr(toptenwin
, ATR_BOLD
, x
);
176 observable_depth(lev
)
180 /* if we ever randomize the order of the elemental planes, we
181 must use a constant external representation in the record file */
182 if (In_endgame(lev
)) {
183 if (Is_astralevel(lev
))
185 else if (Is_waterlevel(lev
))
187 else if (Is_firelevel(lev
))
189 else if (Is_airlevel(lev
))
191 else if (Is_earthlevel(lev
))
200 /* throw away characters until current record has been entirely consumed */
209 } while (c
!= '\n' && c
!= EOF
);
215 struct toptenentry
*tt
;
217 char inbuf
[SCANBUFSZ
], s1
[SCANBUFSZ
], s2
[SCANBUFSZ
], s3
[SCANBUFSZ
],
218 s4
[SCANBUFSZ
], s5
[SCANBUFSZ
], s6
[SCANBUFSZ
];
220 #ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
221 static const char fmt
[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c";
222 static const char fmt32
[] = "%c%c %s %s%*c";
223 static const char fmt33
[] = "%s %s %s %s %s %s%*c";
225 static const char fmt
[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
226 static const char fmt32
[] = "%c%c %[^,],%[^\n]%*c";
227 static const char fmt33
[] = "%s %s %s %s %[^,],%[^\n]%*c";
230 #ifdef UPDATE_RECORD_IN_PLACE
231 /* note: input below must read the record's terminating newline */
232 final_fpos
= tt
->fpos
= ftell(rfile
);
235 if (fscanf(rfile
, fmt
, &tt
->ver_major
, &tt
->ver_minor
, &tt
->patchlevel
,
236 &tt
->points
, &tt
->deathdnum
, &tt
->deathlev
, &tt
->maxlvl
,
237 &tt
->hp
, &tt
->maxhp
, &tt
->deaths
, &tt
->deathdate
,
238 &tt
->birthdate
, &tt
->uid
) != TTFIELDS
) {
241 discardexcess(rfile
);
243 /* load remainder of record into a local buffer;
244 this imposes an implicit length limit of SCANBUFSZ
245 on every string field extracted from the buffer */
246 if (!fgets(inbuf
, sizeof inbuf
, rfile
)) {
247 /* sscanf will fail and tt->points will be set to 0 */
249 } else if (!index(inbuf
, '\n')) {
250 Strcpy(&inbuf
[sizeof inbuf
- 2], "\n");
251 discardexcess(rfile
);
253 /* Check for backwards compatibility */
254 if (tt
->ver_major
< 3 || (tt
->ver_major
== 3 && tt
->ver_minor
< 3)) {
257 if (sscanf(inbuf
, fmt32
, tt
->plrole
, tt
->plgend
, s1
, s2
) == 4) {
258 tt
->plrole
[1] = tt
->plgend
[1] = '\0'; /* read via %c */
259 copynchars(tt
->name
, s1
, (int) (sizeof tt
->name
) - 1);
260 copynchars(tt
->death
, s2
, (int) (sizeof tt
->death
) - 1);
263 tt
->plrole
[1] = '\0';
264 if ((i
= str2role(tt
->plrole
)) >= 0)
265 Strcpy(tt
->plrole
, roles
[i
].filecode
);
266 Strcpy(tt
->plrace
, "?");
267 Strcpy(tt
->plgend
, (tt
->plgend
[0] == 'M') ? "Mal" : "Fem");
268 Strcpy(tt
->plalign
, "?");
269 } else if (sscanf(inbuf
, fmt33
, s1
, s2
, s3
, s4
, s5
, s6
) == 6) {
270 copynchars(tt
->plrole
, s1
, (int) (sizeof tt
->plrole
) - 1);
271 copynchars(tt
->plrace
, s2
, (int) (sizeof tt
->plrace
) - 1);
272 copynchars(tt
->plgend
, s3
, (int) (sizeof tt
->plgend
) - 1);
273 copynchars(tt
->plalign
, s4
, (int) (sizeof tt
->plalign
) - 1);
274 copynchars(tt
->name
, s5
, (int) (sizeof tt
->name
) - 1);
275 copynchars(tt
->death
, s6
, (int) (sizeof tt
->death
) - 1);
279 if (tt
->points
> 0) {
280 nsb_unmung_line(tt
->name
);
281 nsb_unmung_line(tt
->death
);
286 /* check old score entries for Y2K problem and fix whenever found */
287 if (tt
->points
> 0) {
288 if (tt
->birthdate
< 19000000L)
289 tt
->birthdate
+= 19000000L;
290 if (tt
->deathdate
< 19000000L)
291 tt
->deathdate
+= 19000000L;
296 writeentry(rfile
, tt
)
298 struct toptenentry
*tt
;
300 static const char fmt32
[] = "%c%c "; /* role,gender */
301 static const char fmt33
[] = "%s %s %s %s "; /* role,race,gndr,algn */
302 #ifndef NO_SCAN_BRACK
303 static const char fmt0
[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
304 static const char fmtX
[] = "%s,%s\n";
305 #else /* NO_SCAN_BRACK */
306 static const char fmt0
[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d ";
307 static const char fmtX
[] = "%s %s\n";
309 nsb_mung_line(tt
->name
);
310 nsb_mung_line(tt
->death
);
313 (void) fprintf(rfile
, fmt0
, tt
->ver_major
, tt
->ver_minor
, tt
->patchlevel
,
314 tt
->points
, tt
->deathdnum
, tt
->deathlev
, tt
->maxlvl
,
315 tt
->hp
, tt
->maxhp
, tt
->deaths
, tt
->deathdate
,
316 tt
->birthdate
, tt
->uid
);
317 if (tt
->ver_major
< 3 || (tt
->ver_major
== 3 && tt
->ver_minor
< 3))
318 (void) fprintf(rfile
, fmt32
, tt
->plrole
[0], tt
->plgend
[0]);
320 (void) fprintf(rfile
, fmt33
, tt
->plrole
, tt
->plrace
, tt
->plgend
,
322 (void) fprintf(rfile
, fmtX
, onlyspace(tt
->name
) ? "_" : tt
->name
,
326 nsb_unmung_line(tt
->name
);
327 nsb_unmung_line(tt
->death
);
333 /* as tab is never used in eg. plname or death, no need to mangle those. */
335 writexlentry(rfile
, tt
, how
)
337 struct toptenentry
*tt
;
340 #define Fprintf (void) fprintf
341 #define XLOG_SEP '\t' /* xlogfile field separator. */
342 char buf
[BUFSZ
], tmpbuf
[DTHSZ
+ 1];
344 Sprintf(buf
, "version=%d.%d.%d", tt
->ver_major
, tt
->ver_minor
,
346 Sprintf(eos(buf
), "%cpoints=%ld%cdeathdnum=%d%cdeathlev=%d", XLOG_SEP
,
347 tt
->points
, XLOG_SEP
, tt
->deathdnum
, XLOG_SEP
, tt
->deathlev
);
348 Sprintf(eos(buf
), "%cmaxlvl=%d%chp=%d%cmaxhp=%d", XLOG_SEP
, tt
->maxlvl
,
349 XLOG_SEP
, tt
->hp
, XLOG_SEP
, tt
->maxhp
);
350 Sprintf(eos(buf
), "%cdeaths=%d%cdeathdate=%ld%cbirthdate=%ld%cuid=%d",
351 XLOG_SEP
, tt
->deaths
, XLOG_SEP
, tt
->deathdate
, XLOG_SEP
,
352 tt
->birthdate
, XLOG_SEP
, tt
->uid
);
353 Fprintf(rfile
, "%s", buf
);
354 Sprintf(buf
, "%crole=%s%crace=%s%cgender=%s%calign=%s", XLOG_SEP
,
355 tt
->plrole
, XLOG_SEP
, tt
->plrace
, XLOG_SEP
, tt
->plgend
, XLOG_SEP
,
357 /* make a copy of death reason that doesn't include ", while helpless" */
358 formatkiller(tmpbuf
, sizeof tmpbuf
, how
, FALSE
);
359 Fprintf(rfile
, "%s%cname=%s%cdeath=%s",
360 buf
, /* (already includes separator) */
361 XLOG_SEP
, plname
, XLOG_SEP
, tmpbuf
);
363 Fprintf(rfile
, "%cwhile=%s", XLOG_SEP
,
364 multi_reason
? multi_reason
: "helpless");
365 Fprintf(rfile
, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP
,
366 encodeconduct(), XLOG_SEP
, moves
, XLOG_SEP
, encodeachieve());
367 Fprintf(rfile
, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP
,
368 (long) urealtime
.realtime
, XLOG_SEP
,
369 (long) ubirthday
, XLOG_SEP
, (long) urealtime
.finish_time
);
370 Fprintf(rfile
, "%cgender0=%s%calign0=%s", XLOG_SEP
,
371 genders
[flags
.initgend
].filecode
, XLOG_SEP
,
372 aligns
[1 - u
.ualignbase
[A_ORIGINAL
]].filecode
);
373 Fprintf(rfile
, "%cflags=0x%lx", XLOG_SEP
, encodexlogflags());
374 Fprintf(rfile
, "\n");
387 if (!u
.uroleplay
.numbones
)
398 if (!u
.uconduct
.food
)
400 if (!u
.uconduct
.unvegan
)
402 if (!u
.uconduct
.unvegetarian
)
404 if (!u
.uconduct
.gnostic
)
406 if (!u
.uconduct
.weaphit
)
408 if (!u
.uconduct
.killer
)
410 if (!u
.uconduct
.literate
)
412 if (!u
.uconduct
.polypiles
)
414 if (!u
.uconduct
.polyselfs
)
416 if (!u
.uconduct
.wishes
)
418 if (!u
.uconduct
.wisharti
)
420 if (!num_genocides())
433 if (u
.uachieve
.enter_gehennom
)
435 if (u
.uachieve
.menorah
)
439 if (u
.uevent
.invoked
)
441 if (u
.uachieve
.amulet
)
443 if (In_endgame(&u
.uz
))
445 if (Is_astralevel(&u
.uz
))
447 if (u
.uachieve
.ascended
)
449 if (u
.uachieve
.mines_luckstone
)
451 if (u
.uachieve
.finish_sokoban
)
453 if (u
.uachieve
.killed_medusa
)
455 if (u
.uroleplay
.blind
)
457 if (u
.uroleplay
.nudist
)
463 #endif /* XLOGFILE */
467 struct toptenentry
*tt
;
469 struct toptenentry
*ttnext
;
471 while (tt
->points
> 0) {
472 ttnext
= tt
->tt_next
;
485 int rank
, rank0
= -1, rank1
= 0;
486 int occ_cnt
= sysopt
.persmax
;
487 register struct toptenentry
*t0
, *tprev
;
488 struct toptenentry
*t1
;
490 register int flg
= 0;
497 #endif /* XLOGFILE */
500 /* Under DICE 3.0, this crashes the system consistently, apparently due to
501 * corruption of *rfile somewhere. Until I figure this out, just cut out
502 * topten support entirely - at least then the game exits cleanly. --AC
507 /* If we are in the midst of a panic, cut out topten entirely.
508 * topten uses alloc() several times, which will lead to
509 * problems if the panic was the result of an alloc() failure.
511 if (program_state
.panicking
)
514 if (iflags
.toptenwin
) {
515 toptenwin
= create_nhwindow(NHW_TEXT
);
518 #if defined(UNIX) || defined(VMS) || defined(__EMX__)
519 #define HUP if (!program_state.done_hup)
525 restore_colors(); /* make sure the screen is black on white */
527 /* create a new 'topten' entry */
530 t0
->ver_major
= VERSION_MAJOR
;
531 t0
->ver_minor
= VERSION_MINOR
;
532 t0
->patchlevel
= PATCHLEVEL
;
533 t0
->points
= u
.urexp
;
534 t0
->deathdnum
= u
.uz
.dnum
;
535 /* deepest_lev_reached() is in terms of depth(), and reporting the
536 * deepest level reached in the dungeon death occurred in doesn't
537 * seem right, so we have to report the death level in depth() terms
538 * as well (which also seems reasonable since that's all the player
539 * sees on the screen anyway)
541 t0
->deathlev
= observable_depth(&u
.uz
);
542 t0
->maxlvl
= deepest_lev_reached(TRUE
);
544 t0
->maxhp
= u
.uhpmax
;
545 t0
->deaths
= u
.umortality
;
547 copynchars(t0
->plrole
, urole
.filecode
, ROLESZ
);
548 copynchars(t0
->plrace
, urace
.filecode
, ROLESZ
);
549 copynchars(t0
->plgend
, genders
[flags
.female
].filecode
, ROLESZ
);
550 copynchars(t0
->plalign
, aligns
[1 - u
.ualign
.type
].filecode
, ROLESZ
);
551 copynchars(t0
->name
, plname
, NAMSZ
);
552 formatkiller(t0
->death
, sizeof t0
->death
, how
, TRUE
);
553 t0
->birthdate
= yyyymmdd(ubirthday
);
554 t0
->deathdate
= yyyymmdd(when
);
556 #ifdef UPDATE_RECORD_IN_PLACE
560 #ifdef LOGFILE /* used for debugging (who dies of what, where) */
561 if (lock_file(LOGFILE
, SCOREPREFIX
, 10)) {
562 if (!(lfile
= fopen_datafile(LOGFILE
, "a", SCOREPREFIX
))) {
563 HUP
raw_print("Cannot open log file!");
565 writeentry(lfile
, t0
);
566 (void) fclose(lfile
);
568 unlock_file(LOGFILE
);
572 if (lock_file(XLOGFILE
, SCOREPREFIX
, 10)) {
573 if (!(xlfile
= fopen_datafile(XLOGFILE
, "a", SCOREPREFIX
))) {
574 HUP
raw_print("Cannot open extended log file!");
576 writexlentry(xlfile
, t0
, how
);
577 (void) fclose(xlfile
);
579 unlock_file(XLOGFILE
);
581 #endif /* XLOGFILE */
583 if (wizard
|| discover
) {
590 "Since you were in %s mode, the score list will not be checked.",
591 wizard
? "wizard" : "discover");
597 if (!lock_file(RECORD
, SCOREPREFIX
, 60))
600 #ifdef UPDATE_RECORD_IN_PLACE
601 rfile
= fopen_datafile(RECORD
, "r+", SCOREPREFIX
);
603 rfile
= fopen_datafile(RECORD
, "r", SCOREPREFIX
);
607 HUP
raw_print("Cannot open record file!");
612 HUP
topten_print("");
614 /* assure minimum number of points */
615 if (t0
->points
< sysopt
.pointsmin
)
618 t1
= tt_head
= newttentry();
620 /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
622 readentry(rfile
, t1
);
623 if (t1
->points
< sysopt
.pointsmin
)
625 if (rank0
< 0 && t1
->points
< t0
->points
) {
632 #ifdef UPDATE_RECORD_IN_PLACE
633 t0
->fpos
= t1
->fpos
; /* insert here */
637 flg
++; /* ask for a rewrite */
643 if ((sysopt
.pers_is_uid
? t1
->uid
== t0
->uid
644 : strncmp(t1
->name
, t0
->name
, NAMSZ
) == 0)
645 && !strncmp(t1
->plrole
, t0
->plrole
, ROLESZ
) && --occ_cnt
<= 0) {
653 "You didn't beat your previous score of %ld points.",
664 if (rank
<= sysopt
.entrymax
) {
665 t1
->tt_next
= newttentry();
669 if (rank
> sysopt
.entrymax
) {
674 if (flg
) { /* rewrite record file */
675 #ifdef UPDATE_RECORD_IN_PLACE
676 (void) fseek(rfile
, (t0
->fpos
>= 0 ? t0
->fpos
: final_fpos
),
679 (void) fclose(rfile
);
680 if (!(rfile
= fopen_datafile(RECORD
, "w", SCOREPREFIX
))) {
681 HUP
raw_print("Cannot write record file");
683 free_ttlist(tt_head
);
686 #endif /* UPDATE_RECORD_IN_PLACE */
690 topten_print("You made the top ten list!");
695 "You reached the %d%s place on the top %d list.",
696 rank0
, ordin(rank0
), sysopt
.entrymax
);
709 for (rank
= 1; t1
->points
!= 0; rank
++, t1
= t1
->tt_next
) {
711 #ifdef UPDATE_RECORD_IN_PLACE
715 writeentry(rfile
, t1
);
718 if (rank
> flags
.end_top
&& (rank
< rank0
- flags
.end_around
719 || rank
> rank0
+ flags
.end_around
)
721 || (sysopt
.pers_is_uid
723 : strncmp(t1
->name
, t0
->name
, NAMSZ
) == 0)))
725 if (rank
== rank0
- flags
.end_around
726 && rank0
> flags
.end_top
+ flags
.end_around
+ 1 && !flags
.end_own
)
729 outentry(rank
, t1
, FALSE
);
731 outentry(rank
, t1
, TRUE
);
733 outentry(rank
, t1
, TRUE
);
734 outentry(0, t0
, TRUE
);
739 outentry(0, t0
, TRUE
);
740 #ifdef UPDATE_RECORD_IN_PLACE
743 /* if a reasonable way to truncate a file exists, use it */
744 truncate_file(rfile
);
746 /* use sentinel record rather than relying on truncation */
747 t1
->points
= 0L; /* terminates file when read back in */
748 t1
->ver_major
= t1
->ver_minor
= t1
->patchlevel
= 0;
749 t1
->uid
= t1
->deathdnum
= t1
->deathlev
= 0;
750 t1
->maxlvl
= t1
->hp
= t1
->maxhp
= t1
->deaths
= 0;
751 t1
->plrole
[0] = t1
->plrace
[0] = t1
->plgend
[0] = t1
->plalign
[0] = '-';
752 t1
->plrole
[1] = t1
->plrace
[1] = t1
->plgend
[1] = t1
->plalign
[1] = 0;
753 t1
->birthdate
= t1
->deathdate
= yyyymmdd((time_t) 0L);
754 Strcpy(t1
->name
, "@");
755 Strcpy(t1
->death
, "<eod>\n");
756 writeentry(rfile
, t1
);
757 (void) fflush(rfile
);
758 #endif /* TRUNCATE_FILE */
760 #endif /* UPDATE_RECORD_IN_PLACE */
761 (void) fclose(rfile
);
763 free_ttlist(tt_head
);
766 if (iflags
.toptenwin
&& !done_stopprint
)
767 display_nhwindow(toptenwin
, 1);
771 if (iflags
.toptenwin
) {
772 destroy_nhwindow(toptenwin
);
783 Strcpy(linebuf
, " No Points Name");
785 while (bp
< linebuf
+ COLNO
- 9)
787 Strcpy(bp
, "Hp [max]");
788 topten_print(linebuf
);
791 /* so>0: standout line; so=0: ordinary line */
793 outentry(rank
, t1
, so
)
794 struct toptenentry
*t1
;
798 boolean second_line
= TRUE
;
800 char *bp
, hpbuf
[24], linebuf3
[BUFSZ
];
805 Sprintf(eos(linebuf
), "%3d", rank
);
807 Strcat(linebuf
, " ");
809 Sprintf(eos(linebuf
), " %10ld %.10s", t1
->points
? t1
->points
: u
.urexp
,
811 Sprintf(eos(linebuf
), "-%s", t1
->plrole
);
812 if (t1
->plrace
[0] != '?')
813 Sprintf(eos(linebuf
), "-%s", t1
->plrace
);
814 /* Printing of gender and alignment is intentional. It has been
815 * part of the NetHack Geek Code, and illustrates a proper way to
816 * specify a character from the command line.
818 Sprintf(eos(linebuf
), "-%s", t1
->plgend
);
819 if (t1
->plalign
[0] != '?')
820 Sprintf(eos(linebuf
), "-%s ", t1
->plalign
);
822 Strcat(linebuf
, " ");
823 if (!strncmp("escaped", t1
->death
, 7)) {
824 Sprintf(eos(linebuf
), "escaped the dungeon %s[max level %d]",
825 !strncmp(" (", t1
->death
+ 7, 2) ? t1
->death
+ 7 + 2 : "",
827 /* fixup for closing paren in "escaped... with...Amulet)[max..." */
828 if ((bp
= index(linebuf
, ')')) != 0)
829 *bp
= (t1
->deathdnum
== astral_level
.dnum
) ? '\0' : ' ';
831 } else if (!strncmp("ascended", t1
->death
, 8)) {
832 Sprintf(eos(linebuf
), "ascended to demigod%s-hood",
833 (t1
->plgend
[0] == 'F') ? "dess" : "");
836 if (!strncmp(t1
->death
, "quit", 4)) {
837 Strcat(linebuf
, "quit");
839 } else if (!strncmp(t1
->death
, "died of st", 10)) {
840 Strcat(linebuf
, "starved to death");
842 } else if (!strncmp(t1
->death
, "choked", 6)) {
843 Sprintf(eos(linebuf
), "choked on h%s food",
844 (t1
->plgend
[0] == 'F') ? "er" : "is");
845 } else if (!strncmp(t1
->death
, "poisoned", 8)) {
846 Strcat(linebuf
, "was poisoned");
847 } else if (!strncmp(t1
->death
, "crushed", 7)) {
848 Strcat(linebuf
, "was crushed to death");
849 } else if (!strncmp(t1
->death
, "petrified by ", 13)) {
850 Strcat(linebuf
, "turned to stone");
852 Strcat(linebuf
, "died");
854 if (t1
->deathdnum
== astral_level
.dnum
) {
855 const char *arg
, *fmt
= " on the Plane of %s";
857 switch (t1
->deathlev
) {
859 fmt
= " on the %s Plane";
878 Sprintf(eos(linebuf
), fmt
, arg
);
880 Sprintf(eos(linebuf
), " in %s", dungeons
[t1
->deathdnum
].dname
);
881 if (t1
->deathdnum
!= knox_level
.dnum
)
882 Sprintf(eos(linebuf
), " on level %d", t1
->deathlev
);
883 if (t1
->deathlev
!= t1
->maxlvl
)
884 Sprintf(eos(linebuf
), " [max %d]", t1
->maxlvl
);
887 /* kludge for "quit while already on Charon's boat" */
888 if (!strncmp(t1
->death
, "quit ", 5))
889 Strcat(linebuf
, t1
->death
+ 4);
891 Strcat(linebuf
, ".");
893 /* Quit, starved, ascended, and escaped contain no second line */
895 Sprintf(eos(linebuf
), " %c%s.", highc(*(t1
->death
)), t1
->death
+ 1);
897 lngr
= (int) strlen(linebuf
);
899 hpbuf
[0] = '-', hpbuf
[1] = '\0';
901 Sprintf(hpbuf
, "%d", t1
->hp
);
902 /* beginning of hp column after padding (not actually padded yet) */
903 hppos
= COLNO
- (sizeof(" Hp [max]") - 1); /* sizeof(str) includes \0 */
904 while (lngr
>= hppos
) {
905 for (bp
= eos(linebuf
); !(*bp
== ' ' && (bp
- linebuf
< hppos
)); bp
--)
907 /* special case: word is too long, wrap in the middle */
908 if (linebuf
+ 15 >= bp
)
909 bp
= linebuf
+ hppos
- 1;
910 /* special case: if about to wrap in the middle of maximum
911 dungeon depth reached, wrap in front of it instead */
912 if (bp
> linebuf
+ 5 && !strncmp(bp
- 5, " [max", 5))
915 Strcpy(linebuf3
, bp
);
917 Strcpy(linebuf3
, bp
+ 1);
920 while (bp
< linebuf
+ (COLNO
- 1))
923 topten_print_bold(linebuf
);
925 topten_print(linebuf
);
926 Sprintf(linebuf
, "%15s %s", "", linebuf3
);
927 lngr
= strlen(linebuf
);
929 /* beginning of hp column not including padding */
930 hppos
= COLNO
- 7 - (int) strlen(hpbuf
);
933 if (bp
<= linebuf
+ hppos
) {
934 /* pad any necessary blanks to the hit point entry */
935 while (bp
< linebuf
+ hppos
)
938 Sprintf(eos(bp
), " %s[%d]",
939 (t1
->maxhp
< 10) ? " " : (t1
->maxhp
< 100) ? " " : "",
947 while (bp
< linebuf
+ so
)
950 topten_print_bold(linebuf
);
952 topten_print(linebuf
);
956 score_wanted(current_ver
, rank
, t1
, playerct
, players
, uid
)
959 struct toptenentry
*t1
;
961 const char **players
;
967 && (t1
->ver_major
!= VERSION_MAJOR
|| t1
->ver_minor
!= VERSION_MINOR
968 || t1
->patchlevel
!= PATCHLEVEL
))
971 if (sysopt
.pers_is_uid
&& !playerct
&& t1
->uid
== uid
)
974 for (i
= 0; i
< playerct
; i
++) {
975 if (players
[i
][0] == '-' && index("pr", players
[i
][1])
976 && players
[i
][2] == 0 && i
+ 1 < playerct
) {
977 const char *arg
= players
[i
+ 1];
978 if ((players
[i
][1] == 'p'
979 && str2role(arg
) == str2role(t1
->plrole
))
980 || (players
[i
][1] == 'r'
981 && str2race(arg
) == str2race(t1
->plrace
)))
984 } else if (strcmp(players
[i
], "all") == 0
985 || strncmp(t1
->name
, players
[i
], NAMSZ
) == 0
986 || (players
[i
][0] == '-' && players
[i
][1] == t1
->plrole
[0]
987 && players
[i
][2] == 0)
988 || (digit(players
[i
][0]) && rank
<= atoi(players
[i
])))
995 * print selected parts of score list.
996 * argc >= 2, with argv[0] untrustworthy (directory names, et al.),
997 * and argv[1] starting with "-s".
1004 const char **players
;
1006 boolean current_ver
= TRUE
, init_done
= FALSE
;
1007 register struct toptenentry
*t1
;
1009 boolean match_found
= FALSE
;
1013 const char *player0
;
1015 if (argc
< 2 || strncmp(argv
[1], "-s", 2)) {
1016 raw_printf("prscore: bad arguments (%d)", argc
);
1020 rfile
= fopen_datafile(RECORD
, "r", SCOREPREFIX
);
1022 raw_print("Cannot open record file!");
1028 extern winid amii_rawprwin
;
1030 init_nhwindows(&argc
, argv
);
1031 amii_rawprwin
= create_nhwindow(NHW_TEXT
);
1035 /* If the score list isn't after a game, we never went through
1036 * initialization. */
1037 if (wiz1_level
.dlevel
== 0) {
1043 if (!argv
[1][2]) { /* plain "-s" */
1049 if (argc
> 1 && !strcmp(argv
[1], "-v")) {
1050 current_ver
= FALSE
;
1056 if (sysopt
.pers_is_uid
) {
1059 players
= (const char **) 0;
1064 player0
= "all"; /* single user system */
1066 player0
= "hackplayer";
1073 players
= (const char **) ++argv
;
1077 t1
= tt_head
= newttentry();
1078 for (rank
= 1;; rank
++) {
1079 readentry(rfile
, t1
);
1080 if (t1
->points
== 0)
1083 && score_wanted(current_ver
, rank
, t1
, playerct
, players
, uid
))
1085 t1
->tt_next
= newttentry();
1089 (void) fclose(rfile
);
1098 for (rank
= 1; t1
->points
!= 0; rank
++, t1
= t1
->tt_next
) {
1099 if (score_wanted(current_ver
, rank
, t1
, playerct
, players
, uid
))
1100 (void) outentry(rank
, t1
, FALSE
);
1103 Sprintf(pbuf
, "Cannot find any %sentries for ",
1104 current_ver
? "current " : "");
1106 Strcat(pbuf
, "you.");
1109 Strcat(pbuf
, "any of ");
1110 for (i
= 0; i
< playerct
; i
++) {
1111 /* stop printing players if there are too many to fit */
1112 if (strlen(pbuf
) + strlen(players
[i
]) + 2 >= BUFSZ
) {
1113 if (strlen(pbuf
) < BUFSZ
- 4)
1114 Strcat(pbuf
, "...");
1116 Strcpy(pbuf
+ strlen(pbuf
) - 4, "...");
1119 Strcat(pbuf
, players
[i
]);
1120 if (i
< playerct
- 1) {
1121 if (players
[i
][0] == '-' && index("pr", players
[i
][1])
1122 && players
[i
][2] == 0)
1130 raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
1133 raw_printf("Player types are: [-p role] [-r race]");
1135 free_ttlist(tt_head
);
1138 extern winid amii_rawprwin
;
1140 display_nhwindow(amii_rawprwin
, 1);
1141 destroy_nhwindow(amii_rawprwin
);
1142 amii_rawprwin
= WIN_ERR
;
1154 /* Look for this role in the role table */
1155 for (i
= 0; roles
[i
].name
.m
; i
++)
1156 if (!strncmp(plch
, roles
[i
].filecode
, ROLESZ
)) {
1157 if (fem
&& roles
[i
].femalenum
!= NON_PM
)
1158 return roles
[i
].femalenum
;
1159 else if (roles
[i
].malenum
!= NON_PM
)
1160 return roles
[i
].malenum
;
1164 /* this might be from a 3.2.x score for former Elf class */
1165 if (!strcmp(plch
, "E"))
1168 impossible("What weird role is this? (%s)", plch
);
1169 return PM_HUMAN_MUMMY
;
1173 * Get a random player name and class from the high score list,
1175 struct toptenentry
*
1176 get_rnd_toptenentry()
1180 register struct toptenentry
*tt
;
1181 static struct toptenentry tt_buf
;
1183 rfile
= fopen_datafile(RECORD
, "r", SCOREPREFIX
);
1185 impossible("Cannot open record file!");
1190 rank
= rnd(sysopt
.tt_oname_maxrank
);
1192 for (i
= rank
; i
; i
--) {
1193 readentry(rfile
, tt
);
1194 if (tt
->points
== 0)
1198 if (tt
->points
== 0) {
1207 (void) fclose(rfile
);
1213 * Attach random player name and class from high score list
1214 * to an object (for statues or morgue corpses).
1220 struct toptenentry
*tt
;
1222 return (struct obj
*) 0;
1224 tt
= get_rnd_toptenentry();
1227 return (struct obj
*) 0;
1229 set_corpsenm(otmp
, classmon(tt
->plrole
, (tt
->plgend
[0] == 'F')));
1230 otmp
= oname(otmp
, tt
->name
);
1235 #ifdef NO_SCAN_BRACK
1236 /* Lattice scanf isn't up to reading the scorefile. What */
1237 /* follows deals with that; I admit it's ugly. (KL) */
1238 /* Now generally available (KL) */
1243 while ((p
= index(p
, ' ')) != 0)
1251 while ((p
= index(p
, '|')) != 0)
1254 #endif /* NO_SCAN_BRACK */