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