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. */
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
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] */
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))
32 /* Changing NAMSZ can break your existing record/logfile */
39 struct toptenentry
*tt_next
;
40 #ifdef UPDATE_RECORD_IN_PLACE
44 int deathdnum
, deathlev
;
45 int maxlvl
, hp
, maxhp
, deaths
;
46 int ver_major
, ver_minor
, patchlevel
;
47 long deathdate
, birthdate
;
49 char plrole
[ROLESZ
+ 1];
50 char plrace
[ROLESZ
+ 1];
51 char plgend
[ROLESZ
+ 1];
52 char plalign
[ROLESZ
+ 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
*);
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 *);
79 staticfn
void free_ttlist(struct toptenentry
*);
80 staticfn
int classmon(char *);
81 staticfn
int score_wanted(boolean
, int, struct toptenentry
*, int,
84 staticfn
void nsb_mung_line(char *);
85 staticfn
void nsb_unmung_line(char *);
88 /* "killed by",&c ["an"] 'svk.killer.name' */
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 */
107 char c
, *kname
= svk
.killer
.name
;
109 buf
[0] = '\0'; /* lint suppression */
110 switch (svk
.killer
.format
) {
112 impossible("bad killer format? (%d)", svk
.killer
.format
);
115 case NO_KILLER_PREFIX
:
122 (void) strncat(buf
, killed_by_prefix
[how
], siz
- 1);
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.
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) */
144 /* tab is not possible due to use of mungspaces() when naming;
145 it would disrupt xlogfile parsing if it were present */
152 if (incl_helpless
&& gm
.multi
< 0) {
153 /* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */
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 */
165 topten_print(const char *x
)
167 if (gt
.toptenwin
== WIN_ERR
)
170 putstr(gt
.toptenwin
, ATR_NONE
, x
);
174 topten_print_bold(const char *x
)
176 if (gt
.toptenwin
== WIN_ERR
)
179 putstr(gt
.toptenwin
, ATR_BOLD
, x
);
183 observable_depth(d_level
*lev
)
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
))
191 else if (Is_waterlevel(lev
))
193 else if (Is_firelevel(lev
))
195 else if (Is_airlevel(lev
))
197 else if (Is_earthlevel(lev
))
206 /* throw away characters until current record has been entirely consumed */
208 discardexcess(FILE *rfile
)
214 } while (c
!= '\n' && c
!= EOF
);
217 DISABLE_WARNING_FORMAT_NONLITERAL
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";
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";
235 #ifdef UPDATE_RECORD_IN_PLACE
236 /* note: input below must read the record's terminating newline */
237 final_fpos
= tt
->fpos
= ftell(rfile
);
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
) {
246 discardexcess(rfile
);
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 */
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)) {
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);
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);
284 if (tt
->points
> 0) {
285 nsb_unmung_line(tt
->name
);
286 nsb_unmung_line(tt
->death
);
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;
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
);
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]);
323 (void) fprintf(rfile
, fmt33
, tt
->plrole
, tt
->plrace
, tt
->plgend
,
325 (void) fprintf(rfile
, fmtX
, onlyspace(tt
->name
) ? "_" : tt
->name
,
329 nsb_unmung_line(tt
->name
);
330 nsb_unmung_line(tt
->death
);
334 RESTORE_WARNING_FORMAT_NONLITERAL
338 /* as tab is never used in eg. svp.plname or death, no need to mangle those. */
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
,
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
,
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
);
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");
393 encodexlogflags(void)
401 if (!u
.uroleplay
.numbones
)
412 if (!u
.uconduct
.food
)
414 if (!u
.uconduct
.unvegan
)
416 if (!u
.uconduct
.unvegetarian
)
418 if (!u
.uconduct
.gnostic
)
420 if (!u
.uconduct
.weaphit
)
422 if (!u
.uconduct
.killer
)
424 if (!u
.uconduct
.literate
)
426 if (!u
.uconduct
.polypiles
)
428 if (!u
.uconduct
.polyselfs
)
430 if (!u
.uconduct
.wishes
)
432 if (!u
.uconduct
.wisharti
)
434 if (!num_genocides())
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())
445 if (!u
.uconduct
.pets
)
453 boolean secondlong
) /* False: handle achievements 1..31, True: 32..62 */
455 int i
, achidx
, offset
;
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
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);
475 /* add the achievement or conduct comma-separated to string */
477 add_achieveX(char *buf
, const char *achievement
, boolean condition
)
480 if (buf
[0] != '\0') {
483 Strcat(buf
, achievement
);
488 encode_extended_achievements(char *buf
)
491 const char *achievement
= NULL
;
492 int i
, achidx
, absidx
;
495 for (i
= 0; u
.uachieved
[i
]; i
++) {
496 achidx
= u
.uachieved
[i
];
497 absidx
= abs(achidx
);
500 achievement
= "ascended";
503 achievement
= "entered_astral_plane";
506 achievement
= "entered_elemental_planes";
509 achievement
= "obtained_the_amulet_of_yendor";
512 achievement
= "performed_the_invocation_ritual";
515 achievement
= "obtained_the_book_of_the_dead";
518 achievement
= "obtained_the_bell_of_opening";
521 achievement
= "obtained_the_candelabrum_of_invocation";
524 achievement
= "entered_gehennom";
527 achievement
= "defeated_medusa";
530 achievement
= "obtained_the_luckstone_from_the_mines";
533 achievement
= "obtained_the_sokoban_prize";
536 achievement
= "consulted_the_oracle";
539 achievement
= "read_a_discworld_novel";
542 achievement
= "entered_the_gnomish_mines";
545 achievement
= "entered_mine_town";
548 achievement
= "entered_a_shop";
551 achievement
= "entered_a_temple";
554 achievement
= "entered_sokoban";
557 achievement
= "entered_bigroom";
560 achievement
= "learned_castle_drawbridge_tune";
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
);
574 add_achieveX(buf
, achievement
, TRUE
);
581 encode_extended_conducts(char *buf
)
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
);
608 #endif /* XLOGFILE */
611 free_ttlist(struct toptenentry
*tt
)
613 struct toptenentry
*ttnext
;
615 while (tt
->points
> 0) {
616 ttnext
= tt
->tt_next
;
624 topten(int how
, time_t when
)
626 struct toptenentry
*t0
, *tprev
;
627 struct toptenentry
*t1
;
636 int rank
, rank0
= -1, rank1
= 0;
637 int occ_cnt
= sysopt
.persmax
;
639 boolean t0_used
, skip_scores
;
641 #ifdef UPDATE_RECORD_IN_PLACE
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
)
651 if (iflags
.toptenwin
) {
652 gt
.toptenwin
= create_nhwindow(NHW_TEXT
);
655 #if defined(HANGUPHANDLING)
656 #define HUP if (!program_state.done_hup)
662 restore_colors(); /* make sure the screen is black on white */
664 /* create a new 'topten' entry */
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
);
682 t0
->maxhp
= u
.uhpmax
;
683 t0
->deaths
= u
.umortality
;
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
);
694 #ifdef UPDATE_RECORD_IN_PLACE
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!");
703 writeentry(lfile
, t0
);
704 (void) fclose(lfile
);
706 unlock_file(LOGFILE
);
710 if (lock_file(XLOGFILE
, SCOREPREFIX
, 10)) {
711 if (!(xlfile
= fopen_datafile(XLOGFILE
, "a", SCOREPREFIX
))) {
712 HUP
raw_print("Cannot open extended log file!");
714 writexlentry(xlfile
, t0
, how
);
715 (void) fclose(xlfile
);
717 unlock_file(XLOGFILE
);
719 #endif /* XLOGFILE */
721 if (wizard
|| discover
) {
728 "Since you were in %s mode, the score list will not be checked.",
729 wizard
? "wizard" : "discover");
735 if (!lock_file(RECORD
, SCOREPREFIX
, 60))
738 #ifdef UPDATE_RECORD_IN_PLACE
739 rfile
= fopen_datafile(RECORD
, "r+", SCOREPREFIX
);
741 rfile
= fopen_datafile(RECORD
, "r", SCOREPREFIX
);
745 HUP
raw_print("Cannot open record file!");
750 HUP
topten_print("");
752 /* assure minimum number of points */
753 if (t0
->points
< sysopt
.pointsmin
)
756 t1
= tt_head
= newttentry();
758 /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
760 readentry(rfile
, t1
);
761 if (t1
->points
< sysopt
.pointsmin
)
763 if (rank0
< 0 && t1
->points
< t0
->points
) {
770 #ifdef UPDATE_RECORD_IN_PLACE
771 t0
->fpos
= t1
->fpos
; /* insert here */
775 flg
++; /* ask for a rewrite */
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) {
791 "You didn't beat your previous score of %ld points.",
802 if (rank
<= sysopt
.entrymax
) {
803 t1
->tt_next
= newttentry();
807 if (rank
> sysopt
.entrymax
) {
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
);
816 (void) fclose(rfile
);
817 if (!(rfile
= fopen_datafile(RECORD
, "w", SCOREPREFIX
))) {
818 HUP
raw_print("Cannot write record file");
820 free_ttlist(tt_head
);
823 #endif /* UPDATE_RECORD_IN_PLACE */
827 topten_print("You made the top ten list!");
832 "You reached the %d%s place on the top %d list.",
833 rank0
, ordin(rank0
), sysopt
.entrymax
);
839 skip_scores
= !flags
.end_top
&& !flags
.end_around
&& !flags
.end_own
;
844 if (!skip_scores
&& !done_stopprint
)
846 for (t1
= tt_head
, rank
= 1; t1
->points
!= 0; t1
= t1
->tt_next
, ++rank
) {
848 #ifdef UPDATE_RECORD_IN_PLACE
852 writeentry(rfile
, t1
);
853 if (skip_scores
|| done_stopprint
)
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
860 : !strncmp(t1
->name
, t0
->name
, NAMSZ
)))) {
861 if (rank
== rank0
- flags
.end_around
862 && rank0
> flags
.end_top
+ flags
.end_around
+ 1
867 outentry(rank
, t1
, FALSE
);
869 outentry(rank
, t1
, TRUE
);
871 outentry(rank
, t1
, TRUE
);
872 outentry(0, t0
, TRUE
);
877 if (!skip_scores
&& !done_stopprint
)
878 outentry(0, t0
, TRUE
);
879 #ifdef UPDATE_RECORD_IN_PLACE
882 /* if a reasonable way to truncate a file exists, use it */
883 truncate_file(rfile
);
885 /* use sentinel record rather than relying on truncation */
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
);
902 free_ttlist(tt_head
);
905 if (!done_stopprint
) {
906 if (iflags
.toptenwin
) {
907 display_nhwindow(gt
.toptenwin
, TRUE
);
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 */
918 if (iflags
.toptenwin
) {
919 destroy_nhwindow(gt
.toptenwin
);
920 gt
.toptenwin
= WIN_ERR
;
930 Strcpy(linebuf
, " No Points Name");
932 while (bp
< linebuf
+ COLNO
- 9)
934 Strcpy(bp
, "Hp [max]");
935 topten_print(linebuf
);
938 DISABLE_WARNING_FORMAT_NONLITERAL
940 /* so>0: standout line; so=0: ordinary line */
942 outentry(int rank
, struct toptenentry
*t1
, boolean so
)
944 boolean second_line
= TRUE
;
946 char *bp
, hpbuf
[24], linebuf3
[BUFSZ
];
951 Sprintf(eos(linebuf
), "%3d", rank
);
953 Strcat(linebuf
, " ");
955 Sprintf(eos(linebuf
), " %10ld %.10s", t1
->points
? t1
->points
: u
.urexp
,
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
);
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 : "",
973 /* fixup for closing paren in "escaped... with...Amulet)[max..." */
974 if ((bp
= strchr(linebuf
, ')')) != 0)
975 *bp
= (t1
->deathdnum
== astral_level
.dnum
) ? '\0' : ' ';
977 } else if (!strncmp("ascended", t1
->death
, 8)) {
978 Sprintf(eos(linebuf
), "ascended to demigod%s-hood",
979 (t1
->plgend
[0] == 'F') ? "dess" : "");
982 if (!strncmp(t1
->death
, "quit", 4)) {
983 Strcat(linebuf
, "quit");
985 } else if (!strncmp(t1
->death
, "died of st", 10)) {
986 Strcat(linebuf
, "starved to death");
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");
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
) {
1005 fmt
= " on the %s Plane";
1024 Sprintf(eos(linebuf
), fmt
, arg
);
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 */
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
);
1051 hpbuf
[0] = '-', hpbuf
[1] = '\0';
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))
1067 Strcpy(linebuf3
, bp
);
1069 Strcpy(linebuf3
, bp
+ 1);
1072 while (bp
< linebuf
+ (COLNO
- 1))
1075 topten_print_bold(linebuf
);
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
);
1085 if (bp
<= linebuf
+ hppos
) {
1086 /* pad any necessary blanks to the hit point entry */
1087 while (bp
< linebuf
+ hppos
)
1090 Sprintf(eos(bp
), " %s[%d]",
1091 (t1
->maxhp
< 10) ? " " : (t1
->maxhp
< 100) ? " " : "",
1097 while (bp
< linebuf
+ (COLNO
- 1))
1100 topten_print_bold(linebuf
);
1102 topten_print(linebuf
);
1105 RESTORE_WARNING_FORMAT_NONLITERAL
1109 boolean current_ver
,
1111 struct toptenentry
*t1
,
1113 const char **players
,
1116 const char *arg
, *nxt
;
1119 if (current_ver
&& (t1
->ver_major
!= VERSION_MAJOR
1120 || t1
->ver_minor
!= VERSION_MINOR
1121 || t1
->patchlevel
!= PATCHLEVEL
))
1124 if (sysopt
.pers_is_uid
&& !playerct
&& t1
->uid
== uid
)
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
++) {
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
))))
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
)))
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.
1190 prscore(int argc
, char **argv
)
1192 const char **players
, *player0
;
1193 int i
, playerct
, rank
;
1194 struct toptenentry
*t1
;
1196 char pbuf
[BUFSZ
], *p
;
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])
1205 if (ln
< 2 || (strncmp(argv
[1], "-s", 2)
1206 && strcmp(argv
[1], "--scores"))) {
1207 raw_printf("prscore: bad arguments (%d)", argc
);
1211 rfile
= fopen_datafile(RECORD
, "r", SCOREPREFIX
);
1213 raw_print("Cannot open record file!");
1219 extern winid amii_rawprwin
;
1221 init_nhwindows(&argc
, argv
);
1222 amii_rawprwin
= create_nhwindow(NHW_TEXT
);
1226 /* If the score list isn't after a game, we never went through
1227 * initialization. */
1228 if (wiz1_level
.dlevel
== 0) {
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]) {
1239 } else { /* concatenated arg string; use up "-s" but keep argc,argv */
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
;
1252 if (sysopt
.pers_is_uid
) {
1255 players
= (const char **) 0;
1257 player0
= svp
.plname
;
1259 player0
= "all"; /* if no plname[], show all scores
1260 * (possibly filtered by '-v') */
1266 players
= (const char **) ++argv
;
1270 t1
= tt_head
= newttentry();
1271 for (rank
= 1; ; rank
++) {
1272 readentry(rfile
, t1
);
1273 if (t1
->points
== 0)
1276 && score_wanted(current_ver
, rank
, t1
, playerct
, players
, uid
))
1278 t1
->tt_next
= newttentry();
1282 (void) fclose(rfile
);
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
);
1296 Sprintf(pbuf
, "Cannot find any %sentries for ",
1297 current_ver
? "current " : "");
1299 Strcat(pbuf
, "you");
1301 /* minor bug: 'nethack -s -u ziggy' will say "any of"
1302 even though the '-u' doesn't indicate multiple names */
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)) {
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
, "...");
1318 Strcpy(pbuf
+ strlen(pbuf
) - 4, "...");
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)
1331 /* append end-of-sentence punctuation if there is room */
1332 if (strlen(pbuf
) < BUFSZ
- 1)
1335 raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
1337 raw_printf("Player types are: [-p role] [-r race]");
1339 free_ttlist(tt_head
);
1342 extern winid amii_rawprwin
;
1344 display_nhwindow(amii_rawprwin
, 1);
1345 destroy_nhwindow(amii_rawprwin
);
1346 amii_rawprwin
= WIN_ERR
;
1352 classmon(char *plch
)
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
;
1365 /* this might be from a 3.2.x score for former Elf class */
1366 if (!strcmp(plch
, "E"))
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)
1381 struct toptenentry
*tt
;
1382 static struct toptenentry tt_buf
;
1384 rfile
= fopen_datafile(RECORD
, "r", SCOREPREFIX
);
1386 impossible("Cannot open record file!");
1391 rank
= rnd(sysopt
.tt_oname_maxrank
);
1393 for (i
= rank
; i
; i
--) {
1394 readentry(rfile
, tt
);
1395 if (tt
->points
== 0)
1399 if (tt
->points
== 0) {
1408 (void) fclose(rfile
);
1414 * Attach random player name and class from high score list
1415 * to an object (for statues or morgue corpses).
1418 tt_oname(struct obj
*otmp
)
1420 struct toptenentry
*tt
;
1422 return (struct obj
*) 0;
1424 tt
= get_rnd_toptenentry();
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
);
1439 /* Randomly select a topten entry to mimic */
1441 tt_doppel(struct monst
*mon
) {
1442 struct toptenentry
*tt
= rn2(13) ? get_rnd_toptenentry() : NULL
;
1446 ret
= rn1(PM_WIZARD
- PM_ARCHEOLOGIST
+ 1, PM_ARCHEOLOGIST
);
1448 if (tt
->plgend
[0] == 'F')
1450 else if (tt
->plgend
[0] == 'M')
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 */
1457 christen_monst(mon
, tt
->name
);
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) */
1470 while ((p
= strchr(p
, ' ')) != 0)
1478 while ((p
= strchr(p
, '|')) != 0)
1481 #endif /* NO_SCAN_BRACK */