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*/