1    | /*	SCCS Id: @(#)topten.c	3.3	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	60
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)
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 *killed_by_prefix[] = {
80   | 	"killed by ", "choked on ", "poisoned by ", "", "drowned in ",
81   | 	"", "dissolved in ", "crushed to death by ", "petrified by ",
82   | 	"turned to slime 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 (flags.toptenwin) {
268  | 	    toptenwin = create_nhwindow(NHW_TEXT);
269  | 	}
270  | 
271  | #if defined(UNIX) || defined(VMS) || defined(__EMX__)
272  | #define HUP	if (!program_state.done_hup)
273  | #else
274  | #define HUP
275  | #endif
276  | 
277  | #ifdef TOS
278  | 	restore_colors();	/* make sure the screen is black on white */
279  | #endif
280  | 	/* create a new 'topten' entry */
281  | 	t0_used = FALSE;
282  | 	t0 = newttentry();
283  | 	/* deepest_lev_reached() is in terms of depth(), and reporting the
284  | 	 * deepest level reached in the dungeon death occurred in doesn't
285  | 	 * seem right, so we have to report the death level in depth() terms
286  | 	 * as well (which also seems reasonable since that's all the player
287  | 	 * sees on the screen anyway)
288  | 	 */
289  | 	t0->ver_major = VERSION_MAJOR;
290  | 	t0->ver_minor = VERSION_MINOR;
291  | 	t0->patchlevel = PATCHLEVEL;
292  | 	t0->points = u.urexp;
293  | 	t0->deathdnum = u.uz.dnum;
294  | 	t0->deathlev = observable_depth(&u.uz);
295  | 	t0->maxlvl = deepest_lev_reached(TRUE);
296  | 	t0->hp = u.uhp;
297  | 	t0->maxhp = u.uhpmax;
298  | 	t0->deaths = u.umortality;
299  | 	t0->uid = uid;
300  | 	(void) strncpy(t0->plrole, urole.filecode, ROLESZ);
301  | 	t0->plrole[ROLESZ] = '\0';
302  | 	(void) strncpy(t0->plrace, urace.filecode, ROLESZ);
303  | 	t0->plrace[ROLESZ] = '\0';
304  | 	(void) strncpy(t0->plgend, genders[flags.female].filecode, ROLESZ);
305  | 	t0->plgend[ROLESZ] = '\0';
306  | 	(void) strncpy(t0->plalign, aligns[1-u.ualign.type].filecode, ROLESZ);
307  | 	t0->plalign[ROLESZ] = '\0';
308  | 	(void) strncpy(t0->name, plname, NAMSZ);
309  | 	t0->name[NAMSZ] = '\0';
310  | 	t0->death[0] = '\0';
311  | 	switch (killer_format) {
312  | 		default: impossible("bad killer format?");
313  | 		case KILLED_BY_AN:
314  | 			Strcat(t0->death, killed_by_prefix[how]);
315  | 			(void) strncat(t0->death, an(killer),
316  | 						DTHSZ-strlen(t0->death));
317  | 			break;
318  | 		case KILLED_BY:
319  | 			Strcat(t0->death, killed_by_prefix[how]);
320  | 			(void) strncat(t0->death, killer,
321  | 						DTHSZ-strlen(t0->death));
322  | 			break;
323  | 		case NO_KILLER_PREFIX:
324  | 			(void) strncat(t0->death, killer, DTHSZ);
325  | 			break;
326  | 	}
327  | 	t0->birthdate = yyyymmdd(u.ubirthday);
328  | 	t0->deathdate = yyyymmdd((time_t)0L);
329  | 	t0->tt_next = 0;
330  | #ifdef UPDATE_RECORD_IN_PLACE
331  | 	t0->fpos = -1L;
332  | #endif
333  | 
334  | #ifdef LOGFILE		/* used for debugging (who dies of what, where) */
335  | 	if (lock_file(LOGFILE, SCOREPREFIX, 10)) {
336  | 	    if(!(lfile = fopen_datafile(LOGFILE, "a", TRUE))) {
337  | 		HUP raw_print("Cannot open log file!");
338  | 	    } else {
339  | 		writeentry(lfile, t0);
340  | 		(void) fclose(lfile);
341  | 	    }
342  | 	    unlock_file(LOGFILE);
343  | 	}
344  | #endif /* LOGFILE */
345  | 
346  | 	if (wizard || discover) {
347  | 	    if (how != PANICKED) HUP {
348  | 		char pbuf[BUFSZ];
349  | 		topten_print("");
350  | 		Sprintf(pbuf,
351  | 	      "Since you were in %s mode, the score list will not be checked.",
352  | 		    wizard ? "wizard" : "discover");
353  | 		topten_print(pbuf);
354  | 	    }
355  | 	    goto showwin;
356  | 	}
357  | 
358  | 	if (!lock_file(RECORD, SCOREPREFIX, 60))
359  | 		goto destroywin;
360  | 
361  | #ifdef UPDATE_RECORD_IN_PLACE
362  | 	rfile = fopen_datafile(RECORD, "r+", TRUE);
363  | #else
364  | 	rfile = fopen_datafile(RECORD, "r", TRUE);
365  | #endif
366  | 
367  | 	if (!rfile) {
368  | 		HUP raw_print("Cannot open record file!");
369  | 		unlock_file(RECORD);
370  | 		goto destroywin;
371  | 	}
372  | 
373  | 	HUP topten_print("");
374  | 
375  | 	/* assure minimum number of points */
376  | 	if(t0->points < POINTSMIN) t0->points = 0;
377  | 
378  | 	t1 = tt_head = newttentry();
379  | 	tprev = 0;
380  | 	/* rank0: -1 undefined, 0 not_on_list, n n_th on list */
381  | 	for(rank = 1; ; ) {
382  | 	    readentry(rfile, t1);
383  | 	    if (t1->points < POINTSMIN) t1->points = 0;
384  | 	    if(rank0 < 0 && t1->points < t0->points) {
385  | 		rank0 = rank++;
386  | 		if(tprev == 0)
387  | 			tt_head = t0;
388  | 		else
389  | 			tprev->tt_next = t0;
390  | 		t0->tt_next = t1;
391  | #ifdef UPDATE_RECORD_IN_PLACE
392  | 		t0->fpos = t1->fpos;	/* insert here */
393  | #endif
394  | 		t0_used = TRUE;
395  | 		occ_cnt--;
396  | 		flg++;		/* ask for a rewrite */
397  | 	    } else tprev = t1;
398  | 
399  | 	    if(t1->points == 0) break;
400  | 	    if(
401  | #ifdef PERS_IS_UID
402  | 		t1->uid == t0->uid &&
403  | #else
404  | 		strncmp(t1->name, t0->name, NAMSZ) == 0 &&
405  | #endif
406  | 		!strncmp(t1->plrole, t0->plrole, ROLESZ) &&
407  | 		--occ_cnt <= 0) {
408  | 		    if(rank0 < 0) {
409  | 			rank0 = 0;
410  | 			rank1 = rank;
411  | 			HUP {
412  | 			    char pbuf[BUFSZ];
413  | 			    Sprintf(pbuf,
414  | 			  "You didn't beat your previous score of %ld points.",
415  | 				    t1->points);
416  | 			    topten_print(pbuf);
417  | 			    topten_print("");
418  | 			}
419  | 		    }
420  | 		    if(occ_cnt < 0) {
421  | 			flg++;
422  | 			continue;
423  | 		    }
424  | 		}
425  | 	    if(rank <= ENTRYMAX) {
426  | 		t1->tt_next = newttentry();
427  | 		t1 = t1->tt_next;
428  | 		rank++;
429  | 	    }
430  | 	    if(rank > ENTRYMAX) {
431  | 		t1->points = 0;
432  | 		break;
433  | 	    }
434  | 	}
435  | 	if(flg) {	/* rewrite record file */
436  | #ifdef UPDATE_RECORD_IN_PLACE
437  | 		(void) fseek(rfile, (t0->fpos >= 0 ?
438  | 				     t0->fpos : final_fpos), SEEK_SET);
439  | #else
440  | 		(void) fclose(rfile);
441  | 		if(!(rfile = fopen_datafile(RECORD, "w", TRUE))){
442  | 			HUP raw_print("Cannot write record file");
443  | 			unlock_file(RECORD);
444  | 			free_ttlist(tt_head);
445  | 			goto destroywin;
446  | 		}
447  | #endif	/* UPDATE_RECORD_IN_PLACE */
448  | 		if(!done_stopprint) if(rank0 > 0){
449  | 		    if(rank0 <= 10)
450  | 			topten_print("You made the top ten list!");
451  | 		    else {
452  | 			char pbuf[BUFSZ];
453  | 			Sprintf(pbuf,
454  | 			  "You reached the %d%s place on the top %d list.",
455  | 				rank0, ordin(rank0), ENTRYMAX);
456  | 			topten_print(pbuf);
457  | 		    }
458  | 		    topten_print("");
459  | 		}
460  | 	}
461  | 	if(rank0 == 0) rank0 = rank1;
462  | 	if(rank0 <= 0) rank0 = rank;
463  | 	if(!done_stopprint) outheader();
464  | 	t1 = tt_head;
465  | 	for(rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
466  | 	    if(flg
467  | #ifdef UPDATE_RECORD_IN_PLACE
468  | 		    && rank >= rank0
469  | #endif
470  | 		) writeentry(rfile, t1);
471  | 	    if (done_stopprint) continue;
472  | 	    if (rank > flags.end_top &&
473  | 		    (rank < rank0 - flags.end_around ||
474  | 		     rank > rank0 + flags.end_around) &&
475  | 		    (!flags.end_own ||
476  | #ifdef PERS_IS_UID
477  | 					t1->uid != t0->uid
478  | #else
479  | 					strncmp(t1->name, t0->name, NAMSZ)
480  | #endif
481  | 		)) continue;
482  | 	    if (rank == rank0 - flags.end_around &&
483  | 		    rank0 > flags.end_top + flags.end_around + 1 &&
484  | 		    !flags.end_own)
485  | 		topten_print("");
486  | 	    if(rank != rank0)
487  | 		outentry(rank, t1, FALSE);
488  | 	    else if(!rank1)
489  | 		outentry(rank, t1, TRUE);
490  | 	    else {
491  | 		outentry(rank, t1, TRUE);
492  | 		outentry(0, t0, TRUE);
493  | 	    }
494  | 	}
495  | 	if(rank0 >= rank) if(!done_stopprint)
496  | 		outentry(0, t0, TRUE);
497  | #ifdef UPDATE_RECORD_IN_PLACE
498  | 	if (flg) {
499  | # ifdef TRUNCATE_FILE
500  | 	    /* if a reasonable way to truncate a file exists, use it */
501  | 	    truncate_file(rfile);
502  | # else
503  | 	    /* use sentinel record rather than relying on truncation */
504  | 	    t1->points = 0L;	/* terminates file when read back in */
505  | 	    t1->ver_major = t1->ver_minor = t1->patchlevel = 0;
506  | 	    t1->uid = t1->deathdnum = t1->deathlev = 0;
507  | 	    t1->maxlvl = t1->hp = t1->maxhp = t1->deaths = 0;
508  | 	    t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-';
509  | 	    t1->plrole[1] = t1->plrace[1] = t1->plgend[1] = t1->plalign[1] = 0;
510  | 	    t1->birthdate = t1->deathdate = yyyymmdd((time_t)0L);
511  | 	    Strcpy(t1->name, "@");
512  | 	    Strcpy(t1->death, "<eod>\n");
513  | 	    writeentry(rfile, t1);
514  | 	    (void) fflush(rfile);
515  | # endif	/* TRUNCATE_FILE */
516  | 	}
517  | #endif	/* UPDATE_RECORD_IN_PLACE */
518  | 	(void) fclose(rfile);
519  | 	unlock_file(RECORD);
520  | 	free_ttlist(tt_head);
521  | 
522  |   showwin:
523  | 	if (flags.toptenwin && !done_stopprint) display_nhwindow(toptenwin, 1);
524  |   destroywin:
525  | 	if (!t0_used) dealloc_ttentry(t0);
526  | 	if (flags.toptenwin) {
527  | 	    destroy_nhwindow(toptenwin);
528  | 	    toptenwin=WIN_ERR;
529  | 	}
530  | }
531  | 
532  | STATIC_OVL void
533  | outheader()
534  | {
535  | 	char linebuf[BUFSZ];
536  | 	register char *bp;
537  | 
538  | 	Strcpy(linebuf, " No  Points     Name");
539  | 	bp = eos(linebuf);
540  | 	while(bp < linebuf + COLNO - 9) *bp++ = ' ';
541  | 	Strcpy(bp, "Hp [max]");
542  | 	topten_print(linebuf);
543  | }
544  | 
545  | /* so>0: standout line; so=0: ordinary line */
546  | STATIC_OVL void
547  | outentry(rank, t1, so)
548  | struct toptenentry *t1;
549  | int rank;
550  | boolean so;
551  | {
552  | 	boolean second_line = TRUE;
553  | 	char linebuf[BUFSZ];
554  | 	char *bp, hpbuf[24], linebuf3[BUFSZ];
555  | 	int hppos, lngr;
556  | 
557  | 
558  | 	linebuf[0] = '\0';
559  | 	if (rank) Sprintf(eos(linebuf), "%3d", rank);
560  | 	else Strcat(linebuf, "   ");
561  | 
562  | 	Sprintf(eos(linebuf), " %10ld  %.10s", t1->points, t1->name);
563  | 	Sprintf(eos(linebuf), "-%s", t1->plrole);
564  | 	if (t1->plrace[0] != '?')
565  | 		Sprintf(eos(linebuf), "-%s", t1->plrace);
566  | 	/* Printing of gender and alignment is intentional.  It has been
567  | 	 * part of the NetHack Geek Code, and illustrates a proper way to
568  | 	 * specify a character from the command line.
569  | 	 */
570  | 	Sprintf(eos(linebuf), "-%s", t1->plgend);
571  | 	if (t1->plalign[0] != '?')
572  | 		Sprintf(eos(linebuf), "-%s ", t1->plalign);
573  | 	else
574  | 		Strcat(linebuf, " ");
575  | 	if (!strncmp("escaped", t1->death, 7)) {
576  | 	    Sprintf(eos(linebuf), "escaped the dungeon %s[max level %d]",
577  | 		    !strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "",
578  | 		    t1->maxlvl);
579  | 	    /* fixup for closing paren in "escaped... with...Amulet)[max..." */
580  | 	    if ((bp = index(linebuf, ')')) != 0)
581  | 		*bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' ';
582  | 	    second_line = FALSE;
583  | 	} else if (!strncmp("ascended", t1->death, 8)) {
584  | 	    Sprintf(eos(linebuf), "ascended to demigod%s-hood",
585  | 		    (t1->plgend[0] == 'F') ? "dess" : "");
586  | 	    second_line = FALSE;
587  | 	} else {
588  | 	    if (!strncmp(t1->death, "quit", 4)) {
589  | 		Strcat(linebuf, "quit");
590  | 		second_line = FALSE;
591  | 	    } else if (!strncmp(t1->death, "starv", 5)) {
592  | 		Strcat(linebuf, "starved to death");
593  | 		second_line = FALSE;
594  | 	    } else if (!strncmp(t1->death, "choked", 6)) {
595  | 		Sprintf(eos(linebuf), "choked on h%s food",
596  | 			(t1->plgend[0] == 'F') ? "er" : "is");
597  | 	    } else if (!strncmp(t1->death, "poisoned", 8)) {
598  | 		Strcat(linebuf, "was poisoned");
599  | 	    } else if (!strncmp(t1->death, "crushed", 7)) {
600  | 		Strcat(linebuf, "was crushed to death");
601  | 	    } else if (!strncmp(t1->death, "petrified by ", 13)) {
602  | 		Strcat(linebuf, "turned to stone");
603  | 	    } else Strcat(linebuf, "died");
604  | 
605  | 	    if (t1->deathdnum == astral_level.dnum) {
606  | 		const char *arg, *fmt = " on the Plane of %s";
607  | 
608  | 		switch (t1->deathlev) {
609  | 		case -5:
610  | 			fmt = " on the %s Plane";
611  | 			arg = "Astral";	break;
612  | 		case -4:
613  | 			arg = "Water";	break;
614  | 		case -3:
615  | 			arg = "Fire";	break;
616  | 		case -2:
617  | 			arg = "Air";	break;
618  | 		case -1:
619  | 			arg = "Earth";	break;
620  | 		default:
621  | 			arg = "Void";	break;
622  | 		}
623  | 		Sprintf(eos(linebuf), fmt, arg);
624  | 	    } else {
625  | 		Sprintf(eos(linebuf), " in %s on level %d",
626  | 			dungeons[t1->deathdnum].dname, t1->deathlev);
627  | 		if (t1->deathlev != t1->maxlvl)
628  | 		    Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
629  | 	    }
630  | 
631  | 	    /* kludge for "quit while already on Charon's boat" */
632  | 	    if (!strncmp(t1->death, "quit ", 5))
633  | 		Strcat(linebuf, t1->death + 4);
634  | 	}
635  | 	Strcat(linebuf, ".");
636  | 
637  | 	/* Quit, starved, ascended, and escaped contain no second line */
638  | 	if (second_line)
639  | 	    Sprintf(eos(linebuf), "  %c%s.", highc(*(t1->death)), t1->death+1);
640  | 
641  | 	lngr = (int)strlen(linebuf);
642  | 	if (t1->hp <= 0) hpbuf[0] = '-', hpbuf[1] = '\0';
643  | 	else Sprintf(hpbuf, "%d", t1->hp);
644  | 	/* beginning of hp column after padding (not actually padded yet) */
645  | 	hppos = COLNO - (sizeof("  Hp [max]")-1); /* sizeof(str) includes \0 */
646  | 	while (lngr >= hppos) {
647  | 	    for(bp = eos(linebuf);
648  | 		    !(*bp == ' ' && (bp-linebuf < hppos));
649  | 		    bp--)
650  | 		;
651  | 	    /* special case: if about to wrap in the middle of maximum
652  | 	       dungeon depth reached, wrap in front of it instead */
653  | 	    if (bp > linebuf + 5 && !strncmp(bp - 5, " [max", 5)) bp -= 5;
654  | 	    Strcpy(linebuf3, bp+1);
655  | 	    *bp = 0;
656  | 	    if (so) {
657  | 		while (bp < linebuf + (COLNO-1)) *bp++ = ' ';
658  | 		*bp = 0;
659  | 		topten_print_bold(linebuf);
660  | 	    } else
661  | 		topten_print(linebuf);
662  | 	    Sprintf(linebuf, "%15s %s", "", linebuf3);
663  | 	    lngr = strlen(linebuf);
664  | 	}
665  | 	/* beginning of hp column not including padding */
666  | 	hppos = COLNO - 7 - (int)strlen(hpbuf);
667  | 	bp = eos(linebuf);
668  | 
669  | 	if (bp <= linebuf + hppos) {
670  | 	    /* pad any necessary blanks to the hit point entry */
671  | 	    while (bp < linebuf + hppos) *bp++ = ' ';
672  | 	    Strcpy(bp, hpbuf);
673  | 	    Sprintf(eos(bp), " %s[%d]",
674  | 		    (t1->maxhp < 10) ? "  " : (t1->maxhp < 100) ? " " : "",
675  | 		    t1->maxhp);
676  | 	}
677  | 
678  | 	if (so) {
679  | 	    bp = eos(linebuf);
680  | 	    if (so >= COLNO) so = COLNO-1;
681  | 	    while (bp < linebuf + so) *bp++ = ' ';
682  | 	    *bp = 0;
683  | 	    topten_print_bold(linebuf);
684  | 	} else
685  | 	    topten_print(linebuf);
686  | }
687  | 
688  | STATIC_OVL int
689  | score_wanted(current_ver, rank, t1, playerct, players, uid)
690  | boolean current_ver;
691  | int rank;
692  | struct toptenentry *t1;
693  | int playerct;
694  | const char **players;
695  | int uid;
696  | {
697  | 	int i;
698  | 
699  | 	if (current_ver && (t1->ver_major != VERSION_MAJOR ||
700  | 			    t1->ver_minor != VERSION_MINOR ||
701  | 			    t1->patchlevel != PATCHLEVEL))
702  | 		return 0;
703  | 
704  | #ifdef PERS_IS_UID
705  | 	if (!playerct && t1->uid == uid)
706  | 		return 1;
707  | #endif
708  | 
709  | 	for (i = 0; i < playerct; i++) {
710  | 		if (strcmp(players[i], "all") == 0 ||
711  | 		    strncmp(t1->name, players[i], NAMSZ) == 0 ||
712  | 		    (players[i][0] == '-' &&
713  | 		     players[i][1] == t1->plrole[0] &&
714  | 		     players[i][2] == 0) ||
715  | 		    (digit(players[i][0]) && rank <= atoi(players[i])))
716  | 		return 1;
717  | 	}
718  | 	return 0;
719  | }
720  | 
721  | /*
722  |  * print selected parts of score list.
723  |  * argc >= 2, with argv[0] untrustworthy (directory names, et al.),
724  |  * and argv[1] starting with "-s".
725  |  */
726  | void
727  | prscore(argc,argv)
728  | int argc;
729  | char **argv;
730  | {
731  | 	const char **players;
732  | 	int playerct, rank;
733  | 	boolean current_ver = TRUE, init_done = FALSE;
734  | 	register struct toptenentry *t1;
735  | 	FILE *rfile;
736  | 	boolean match_found = FALSE;
737  | 	register int i;
738  | 	char pbuf[BUFSZ];
739  | 	int uid = -1;
740  | #ifndef PERS_IS_UID
741  | 	const char *player0;
742  | #endif
743  | 
744  | 	if (argc < 2 || strncmp(argv[1], "-s", 2)) {
745  | 		raw_printf("prscore: bad arguments (%d)", argc);
746  | 		return;
747  | 	}
748  | 
749  | 	rfile = fopen_datafile(RECORD, "r", TRUE);
750  | 	if (!rfile) {
751  | 		raw_print("Cannot open record file!");
752  | 		return;
753  | 	}
754  | 
755  | #ifdef	AMIGA
756  | 	{
757  | 	    extern winid amii_rawprwin;
758  | 	    init_nhwindows(&argc, argv);
759  | 	    amii_rawprwin = create_nhwindow(NHW_TEXT);
760  | 	}
761  | #endif
762  | 
763  | 	/* If the score list isn't after a game, we never went through
764  | 	 * initialization. */
765  | 	if (wiz1_level.dlevel == 0) {
766  | 		dlb_init();
767  | 		init_dungeons();
768  | 		init_done = TRUE;
769  | 	}
770  | 
771  | 	if (!argv[1][2]){	/* plain "-s" */
772  | 		argc--;
773  | 		argv++;
774  | #if 0 /* uses obsolete pl_classes[] */
775  | 	} else if (!argv[1][3] && index(pl_classes, argv[1][2])) {
776  | 		/* may get this case instead of next accidentally,
777  | 		 * but neither is listed in the documentation, so
778  | 		 * anything useful that happens is a bonus anyway */
779  | 		argv[1]++;
780  | 		argv[1][0] = '-';
781  | #endif
782  | 	} else	argv[1] += 2;
783  | 
784  | 	if (argc > 1 && !strcmp(argv[1], "-v")) {
785  | 		current_ver = FALSE;
786  | 		argc--;
787  | 		argv++;
788  | 	}
789  | 
790  | 	if (argc <= 1) {
791  | #ifdef PERS_IS_UID
792  | 		uid = getuid();
793  | 		playerct = 0;
794  | 		players = (const char **)0;
795  | #else
796  | 		player0 = plname;
797  | 		if (!*player0)
798  | # ifdef AMIGA
799  | 			player0 = "all";	/* single user system */
800  | # else
801  | 			player0 = "hackplayer";
802  | # endif
803  | 		playerct = 1;
804  | 		players = &player0;
805  | #endif
806  | 	} else {
807  | 		playerct = --argc;
808  | 		players = (const char **)++argv;
809  | 	}
810  | 	raw_print("");
811  | 
812  | 	t1 = tt_head = newttentry();
813  | 	for (rank = 1; ; rank++) {
814  | 	    readentry(rfile, t1);
815  | 	    if (t1->points == 0) break;
816  | 	    if (!match_found &&
817  | 		    score_wanted(current_ver, rank, t1, playerct, players, uid))
818  | 		match_found = TRUE;
819  | 	    t1->tt_next = newttentry();
820  | 	    t1 = t1->tt_next;
821  | 	}
822  | 
823  | 	(void) fclose(rfile);
824  | 	if (init_done) {
825  | 	    free_dungeons();
826  | 	    dlb_cleanup();
827  | 	}
828  | 
829  | 	if (match_found) {
830  | 	    outheader();
831  | 	    t1 = tt_head;
832  | 	    for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
833  | 		if (score_wanted(current_ver, rank, t1, playerct, players, uid))
834  | 		    (void) outentry(rank, t1, 0);
835  | 	    }
836  | 	} else {
837  | 	    Sprintf(pbuf, "Cannot find any %sentries for ",
838  | 				current_ver ? "current " : "");
839  | 	    if (playerct < 1) Strcat(pbuf, "you.");
840  | 	    else {
841  | 		if (playerct > 1) Strcat(pbuf, "any of ");
842  | 		for (i = 0; i < playerct; i++) {
843  | 		    Strcat(pbuf, players[i]);
844  | 		    if (i < playerct-1) Strcat(pbuf, ":");
845  | 		}
846  | 	    }
847  | 	    raw_print(pbuf);
848  | 	    raw_printf("Call is: %s -s [-v] [-role] [maxrank] [playernames]",
849  | 			 hname);
850  | 	}
851  | 	free_ttlist(tt_head);
852  | #ifdef	AMIGA
853  | 	display_nhwindow(amii_rawprwin, 1);
854  | 	destroy_nhwindow(amii_rawprwin);
855  | 	amii_rawprwin = WIN_ERR;
856  | #endif
857  | }
858  | 
859  | STATIC_OVL int
860  | classmon(plch, fem)
861  | 	char *plch;
862  | 	boolean fem;
863  | {
864  | 	int i;
865  | 
866  | 	/* Look for this role in the role table */
867  | 	for (i = 0; roles[i].name.m; i++)
868  | 	    if (!strncmp(plch, roles[i].filecode, ROLESZ)) {
869  | 		if (fem && roles[i].femalenum != NON_PM)
870  | 		    return roles[i].femalenum;
871  | 		else if (roles[i].malenum != NON_PM)
872  | 		    return roles[i].malenum;
873  | 		else
874  | 		    return PM_HUMAN;
875  | 	    }
876  | 	/* this might be from a 3.2.x score for former Elf class */
877  | 	if (!strcmp(plch, "E")) return PM_RANGER;
878  | 
879  | 	impossible("What weird role is this? (%s)", plch);
880  | 	return (PM_HUMAN_MUMMY);
881  | }
882  | 
883  | /*
884  |  * Get a random player name and class from the high score list,
885  |  * and attach them to an object (for statues or morgue corpses).
886  |  */
887  | struct obj *
888  | tt_oname(otmp)
889  | struct obj *otmp;
890  | {
891  | 	int rank;
892  | 	register int i;
893  | 	register struct toptenentry *tt;
894  | 	FILE *rfile;
895  | 	struct toptenentry tt_buf;
896  | 
897  | 	if (!otmp) return((struct obj *) 0);
898  | 
899  | 	rfile = fopen_datafile(RECORD, "r", TRUE);
900  | 	if (!rfile) {
901  | 		impossible("Cannot open record file!");
902  | 		return (struct obj *)0;
903  | 	}
904  | 
905  | 	tt = &tt_buf;
906  | 	rank = rnd(10);
907  | pickentry:
908  | 	for(i = rank; i; i--) {
909  | 	    readentry(rfile, tt);
910  | 	    if(tt->points == 0) break;
911  | 	}
912  | 
913  | 	if(tt->points == 0) {
914  | 		if(rank > 1) {
915  | 			rank = 1;
916  | 			rewind(rfile);
917  | 			goto pickentry;
918  | 		}
919  | 		otmp = (struct obj *) 0;
920  | 	} else {
921  | 		/* reset timer in case corpse started out as lizard or troll */
922  | 		if (otmp->otyp == CORPSE) obj_stop_timers(otmp);
923  | 		otmp->corpsenm = classmon(tt->plrole, (tt->plgend[0] == 'F'));
924  | 		otmp->owt = weight(otmp);
925  | 		otmp = oname(otmp, tt->name);
926  | 		if (otmp->otyp == CORPSE) start_corpse_timeout(otmp);
927  | 	}
928  | 
929  | 	(void) fclose(rfile);
930  | 	return otmp;
931  | }
932  | 
933  | #ifdef NO_SCAN_BRACK
934  | /* Lattice scanf isn't up to reading the scorefile.  What */
935  | /* follows deals with that; I admit it's ugly. (KL) */
936  | /* Now generally available (KL) */
937  | STATIC_OVL void
938  | nsb_mung_line(p)
939  | 	char *p;
940  | {
941  | 	while ((p = index(p, ' ')) != 0) *p = '|';
942  | }
943  | 
944  | STATIC_OVL void
945  | nsb_unmung_line(p)
946  | 	char *p;
947  | {
948  | 	while ((p = index(p, '|')) != 0) *p = ' ';
949  | }
950  | #endif /* NO_SCAN_BRACK */
951  | 
952  | /*topten.c*/