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
26static 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
43struct 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
62STATIC_DCL void FDECL(topten_print, (const char *));
63STATIC_DCL void FDECL(topten_print_bold, (const char *));
64STATIC_DCL xchar FDECL(observable_depth, (d_level *));
65STATIC_DCL void NDECL(outheader);
66STATIC_DCL void FDECL(outentry, (int,struct toptenentry *,BOOLEAN_P));
67STATIC_DCL void FDECL(readentry, (FILE *,struct toptenentry *));
68STATIC_DCL void FDECL(writeentry, (FILE *,struct toptenentry *));
69STATIC_DCL void FDECL(free_ttlist, (struct toptenentry *));
70STATIC_DCL int FDECL(classmon, (char *,BOOLEAN_P));
71STATIC_DCL int FDECL(score_wanted,
72		(BOOLEAN_P, int,struct toptenentry *,int,const char **,int));
73#ifdef NO_SCAN_BRACK
74STATIC_DCL void FDECL(nsb_mung_line,(char*));
75STATIC_DCL void FDECL(nsb_unmung_line,(char*));
76#endif
77
78/* must fit with end.c; used in rip.c */
79NEARDATA 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
85static winid toptenwin = WIN_ERR;
86
87STATIC_OVL void
88topten_print(x)
89const char *x;
90{
91	if (toptenwin == WIN_ERR)
92	    raw_print(x);
93	else
94	    putstr(toptenwin, ATR_NONE, x);
95}
96
97STATIC_OVL void
98topten_print_bold(x)
99const char *x;
100{
101	if (toptenwin == WIN_ERR)
102	    raw_print_bold(x);
103	else
104	    putstr(toptenwin, ATR_BOLD, x);
105}
106
107STATIC_OVL xchar
108observable_depth(lev)
109d_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
125STATIC_OVL void
126readentry(rfile,tt)
127FILE *rfile;
128struct 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
188STATIC_OVL void
189writeentry(rfile,tt)
190FILE *rfile;
191struct 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
229STATIC_OVL void
230free_ttlist(tt)
231struct 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
243void
244topten(how)
245int 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
539STATIC_OVL void
540outheader()
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 */
553STATIC_OVL void
554outentry(rank, t1, so)
555struct toptenentry *t1;
556int rank;
557boolean 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
696STATIC_OVL int
697score_wanted(current_ver, rank, t1, playerct, players, uid)
698boolean current_ver;
699int rank;
700struct toptenentry *t1;
701int playerct;
702const char **players;
703int 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 */
743void
744prscore(argc,argv)
745int argc;
746char **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
884STATIC_OVL int
885classmon(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 */
912struct obj *
913tt_oname(otmp)
914struct 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);
932pickentry:
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) */
962STATIC_OVL void
963nsb_mung_line(p)
964	char *p;
965{
966	while ((p = index(p, ' ')) != 0) *p = '|';
967}
968
969STATIC_OVL void
970nsb_unmung_line(p)
971	char *p;
972{
973	while ((p = index(p, '|')) != 0) *p = ' ';
974}
975#endif /* NO_SCAN_BRACK */
976
977/*topten.c*/
978