Files
nethack/src/topten.c
PatR 532cddbd35 topten's so (standout) handling
This fixes the broken code that was using a boolean as an integer.
I didn't try to track down when it changed or what it looked like
before the change.  The intended effect is fairly straightforward;
just padding a bold line with spaces.  I've no idea why someone
deciced that that was useful though.

It also fixes something I broke six years ago:  tty_exit_nhwindows()
releases the termcap data needed for turning bold on and off, so
raw_print_bold() used by topten() stopped working on tty then.

Not fixed:  the code in really_done() for dealing with topten() vs
the 'toptenwin' option really ought to be redone.
2022-01-31 18:20:34 -08:00

1391 lines
42 KiB
C

/* NetHack 3.7 topten.c $NHDT-Date: 1606009004 2020/11/22 01:36:44 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.74 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "dlb.h"
#if defined(VMS) && !defined(UPDATE_RECORD_IN_PLACE)
/* We don't want to rewrite the whole file, because that entails
creating a new version which requires that the old one be deletable.
[Write and Delete are separate permissions on VMS. 'record' should
be writable but not deletable there.] */
#define UPDATE_RECORD_IN_PLACE
#endif
/*
* Updating in place can leave junk at the end of the file in some
* circumstances (if it shrinks and the O.S. doesn't have a straightforward
* way to truncate it). The trailing junk is harmless and the code
* which reads the scores will ignore it.
*/
#ifdef UPDATE_RECORD_IN_PLACE
static long final_fpos; /* [note: do not move this to the 'g' struct] */
#endif
#define done_stopprint g.program_state.stopprint
#define newttentry() (struct toptenentry *) alloc(sizeof (struct toptenentry))
#define dealloc_ttentry(ttent) free((genericptr_t) (ttent))
#ifndef NAMSZ
/* Changing NAMSZ can break your existing record/logfile */
#define NAMSZ 10
#endif
#define DTHSZ 100
#define ROLESZ 3
struct toptenentry {
struct toptenentry *tt_next;
#ifdef UPDATE_RECORD_IN_PLACE
long fpos;
#endif
long points;
int deathdnum, deathlev;
int maxlvl, hp, maxhp, deaths;
int ver_major, ver_minor, patchlevel;
long deathdate, birthdate;
int uid;
char plrole[ROLESZ + 1];
char plrace[ROLESZ + 1];
char plgend[ROLESZ + 1];
char plalign[ROLESZ + 1];
char name[NAMSZ + 1];
char death[DTHSZ + 1];
} *tt_head;
/* size big enough to read in all the string fields at once; includes
room for separating space or trailing newline plus string terminator */
#define SCANBUFSZ (4 * (ROLESZ + 1) + (NAMSZ + 1) + (DTHSZ + 1) + 1)
static struct toptenentry zerott;
static void topten_print(const char *);
static void topten_print_bold(const char *);
static void outheader(void);
static void outentry(int, struct toptenentry *, boolean);
static void discardexcess(FILE *);
static void readentry(FILE *, struct toptenentry *);
static void writeentry(FILE *, struct toptenentry *);
#ifdef XLOGFILE
static void writexlentry(FILE *, struct toptenentry *, int);
static long encodexlogflags(void);
static long encodeconduct(void);
static long encodeachieve(boolean);
static void add_achieveX(char *, const char *, boolean);
static char *encode_extended_achievements(void);
static char *encode_extended_conducts(void);
#endif
static void free_ttlist(struct toptenentry *);
static int classmon(char *);
static int score_wanted(boolean, int, struct toptenentry *, int,
const char **, int);
#ifdef NO_SCAN_BRACK
static void nsb_mung_line(char *);
static void nsb_unmung_line(char *);
#endif
/* "killed by",&c ["an"] 'g.killer.name' */
void
formatkiller(
char *buf,
unsigned siz,
int how,
boolean incl_helpless)
{
static NEARDATA const char *const killed_by_prefix[] = {
/* DIED, CHOKING, POISONING, STARVING, */
"killed by ", "choked on ", "poisoned by ", "died of ",
/* DROWNING, BURNING, DISSOLVED, CRUSHING, */
"drowned in ", "burned by ", "dissolved in ", "crushed to death by ",
/* STONING, TURNED_SLIME, GENOCIDED, */
"petrified by ", "turned to slime by ", "killed by ",
/* PANICKED, TRICKED, QUIT, ESCAPED, ASCENDED */
"", "", "", "", ""
};
unsigned l;
char c, *kname = g.killer.name;
buf[0] = '\0'; /* lint suppression */
switch (g.killer.format) {
default:
impossible("bad killer format? (%d)", g.killer.format);
/*FALLTHRU*/
case NO_KILLER_PREFIX:
break;
case KILLED_BY_AN:
kname = an(kname);
/*FALLTHRU*/
case KILLED_BY:
(void) strncat(buf, killed_by_prefix[how], siz - 1);
l = strlen(buf);
buf += l, siz -= l;
break;
}
/* Copy kname into buf[].
* Object names and named fruit have already been sanitized, but
* monsters can have "called 'arbitrary text'" attached to them,
* so make sure that that text can't confuse field splitting when
* record, logfile, or xlogfile is re-read at some later point.
*/
while (--siz > 0) {
c = *kname++;
if (!c)
break;
else if (c == ',')
c = ';';
/* 'xlogfile' doesn't really need protection for '=', but
fixrecord.awk for corrupted 3.6.0 'record' does (only
if using xlogfile rather than logfile to repair record) */
else if (c == '=')
c = '_';
/* tab is not possible due to use of mungspaces() when naming;
it would disrupt xlogfile parsing if it were present */
else if (c == '\t')
c = ' ';
*buf++ = c;
}
*buf = '\0';
if (incl_helpless && g.multi) {
/* X <= siz: 'sizeof "string"' includes 1 for '\0' terminator */
if (g.multi_reason
&& strlen(g.multi_reason) + sizeof ", while " <= siz)
Sprintf(buf, ", while %s", g.multi_reason);
/* either g.multi_reason wasn't specified or wouldn't fit */
else if (sizeof ", while helpless" <= siz)
Strcpy(buf, ", while helpless");
/* else extra death info won't fit, so leave it out */
}
}
static void
topten_print(const char *x)
{
if (g.toptenwin == WIN_ERR)
raw_print(x);
else
putstr(g.toptenwin, ATR_NONE, x);
}
static void
topten_print_bold(const char *x)
{
if (g.toptenwin == WIN_ERR)
raw_print_bold(x);
else
putstr(g.toptenwin, ATR_BOLD, x);
}
int
observable_depth(d_level* lev)
{
#if 0
/* if we ever randomize the order of the elemental planes, we
must use a constant external representation in the record file */
if (In_endgame(lev)) {
if (Is_astralevel(lev))
return -5;
else if (Is_waterlevel(lev))
return -4;
else if (Is_firelevel(lev))
return -3;
else if (Is_airlevel(lev))
return -2;
else if (Is_earthlevel(lev))
return -1;
else
return 0; /* ? */
} else
#endif
return depth(lev);
}
/* throw away characters until current record has been entirely consumed */
static void
discardexcess(FILE* rfile)
{
int c;
do {
c = fgetc(rfile);
} while (c != '\n' && c != EOF);
}
DISABLE_WARNING_FORMAT_NONLITERAL
static void
readentry(FILE* rfile, struct toptenentry* tt)
{
char inbuf[SCANBUFSZ], s1[SCANBUFSZ], s2[SCANBUFSZ], s3[SCANBUFSZ],
s4[SCANBUFSZ], s5[SCANBUFSZ], s6[SCANBUFSZ];
#ifdef NO_SCAN_BRACK /* Version_ Pts DgnLevs_ Hp___ Died__Born id */
static const char fmt[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d%*c";
static const char fmt32[] = "%c%c %s %s%*c";
static const char fmt33[] = "%s %s %s %s %s %s%*c";
#else
static const char fmt[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
static const char fmt32[] = "%c%c %[^,],%[^\n]%*c";
static const char fmt33[] = "%s %s %s %s %[^,],%[^\n]%*c";
#endif
#ifdef UPDATE_RECORD_IN_PLACE
/* note: input below must read the record's terminating newline */
final_fpos = tt->fpos = ftell(rfile);
#endif
#define TTFIELDS 13
if (fscanf(rfile, fmt, &tt->ver_major, &tt->ver_minor, &tt->patchlevel,
&tt->points, &tt->deathdnum, &tt->deathlev, &tt->maxlvl,
&tt->hp, &tt->maxhp, &tt->deaths, &tt->deathdate,
&tt->birthdate, &tt->uid) != TTFIELDS) {
#undef TTFIELDS
tt->points = 0;
discardexcess(rfile);
} else {
/* load remainder of record into a local buffer;
this imposes an implicit length limit of SCANBUFSZ
on every string field extracted from the buffer */
if (!fgets(inbuf, sizeof inbuf, rfile)) {
/* sscanf will fail and tt->points will be set to 0 */
*inbuf = '\0';
} else if (!index(inbuf, '\n')) {
Strcpy(&inbuf[sizeof inbuf - 2], "\n");
discardexcess(rfile);
}
/* Check for backwards compatibility */
if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3)) {
int i;
if (sscanf(inbuf, fmt32, tt->plrole, tt->plgend, s1, s2) == 4) {
tt->plrole[1] = tt->plgend[1] = '\0'; /* read via %c */
copynchars(tt->name, s1, (int) (sizeof tt->name) - 1);
copynchars(tt->death, s2, (int) (sizeof tt->death) - 1);
} else
tt->points = 0;
tt->plrole[1] = '\0';
if ((i = str2role(tt->plrole)) >= 0)
Strcpy(tt->plrole, roles[i].filecode);
Strcpy(tt->plrace, "?");
Strcpy(tt->plgend, (tt->plgend[0] == 'M') ? "Mal" : "Fem");
Strcpy(tt->plalign, "?");
} else if (sscanf(inbuf, fmt33, s1, s2, s3, s4, s5, s6) == 6) {
copynchars(tt->plrole, s1, (int) (sizeof tt->plrole) - 1);
copynchars(tt->plrace, s2, (int) (sizeof tt->plrace) - 1);
copynchars(tt->plgend, s3, (int) (sizeof tt->plgend) - 1);
copynchars(tt->plalign, s4, (int) (sizeof tt->plalign) - 1);
copynchars(tt->name, s5, (int) (sizeof tt->name) - 1);
copynchars(tt->death, s6, (int) (sizeof tt->death) - 1);
} else
tt->points = 0;
#ifdef NO_SCAN_BRACK
if (tt->points > 0) {
nsb_unmung_line(tt->name);
nsb_unmung_line(tt->death);
}
#endif
}
/* check old score entries for Y2K problem and fix whenever found */
if (tt->points > 0) {
if (tt->birthdate < 19000000L)
tt->birthdate += 19000000L;
if (tt->deathdate < 19000000L)
tt->deathdate += 19000000L;
}
}
static void
writeentry(FILE* rfile, struct toptenentry* tt)
{
static const char fmt32[] = "%c%c "; /* role,gender */
static const char fmt33[] = "%s %s %s %s "; /* role,race,gndr,algn */
#ifndef NO_SCAN_BRACK
static const char fmt0[] = "%d.%d.%d %ld %d %d %d %d %d %d %ld %ld %d ";
static const char fmtX[] = "%s,%s\n";
#else /* NO_SCAN_BRACK */
static const char fmt0[] = "%d %d %d %ld %d %d %d %d %d %d %ld %ld %d ";
static const char fmtX[] = "%s %s\n";
nsb_mung_line(tt->name);
nsb_mung_line(tt->death);
#endif
(void) fprintf(rfile, fmt0, tt->ver_major, tt->ver_minor, tt->patchlevel,
tt->points, tt->deathdnum, tt->deathlev, tt->maxlvl,
tt->hp, tt->maxhp, tt->deaths, tt->deathdate,
tt->birthdate, tt->uid);
if (tt->ver_major < 3 || (tt->ver_major == 3 && tt->ver_minor < 3))
(void) fprintf(rfile, fmt32, tt->plrole[0], tt->plgend[0]);
else
(void) fprintf(rfile, fmt33, tt->plrole, tt->plrace, tt->plgend,
tt->plalign);
(void) fprintf(rfile, fmtX, onlyspace(tt->name) ? "_" : tt->name,
tt->death);
#ifdef NO_SCAN_BRACK
nsb_unmung_line(tt->name);
nsb_unmung_line(tt->death);
#endif
}
RESTORE_WARNING_FORMAT_NONLITERAL
#ifdef XLOGFILE
/* as tab is never used in eg. g.plname or death, no need to mangle those. */
static void
writexlentry(FILE* rfile, struct toptenentry* tt, int how)
{
#define Fprintf (void) fprintf
#define XLOG_SEP '\t' /* xlogfile field separator. */
char buf[BUFSZ], tmpbuf[DTHSZ + 1];
Sprintf(buf, "version=%d.%d.%d", tt->ver_major, tt->ver_minor,
tt->patchlevel);
Sprintf(eos(buf), "%cpoints=%ld%cdeathdnum=%d%cdeathlev=%d", XLOG_SEP,
tt->points, XLOG_SEP, tt->deathdnum, XLOG_SEP, tt->deathlev);
Sprintf(eos(buf), "%cmaxlvl=%d%chp=%d%cmaxhp=%d", XLOG_SEP, tt->maxlvl,
XLOG_SEP, tt->hp, XLOG_SEP, tt->maxhp);
Sprintf(eos(buf), "%cdeaths=%d%cdeathdate=%ld%cbirthdate=%ld%cuid=%d",
XLOG_SEP, tt->deaths, XLOG_SEP, tt->deathdate, XLOG_SEP,
tt->birthdate, XLOG_SEP, tt->uid);
Fprintf(rfile, "%s", buf);
Sprintf(buf, "%crole=%s%crace=%s%cgender=%s%calign=%s", XLOG_SEP,
tt->plrole, XLOG_SEP, tt->plrace, XLOG_SEP, tt->plgend, XLOG_SEP,
tt->plalign);
/* make a copy of death reason that doesn't include ", while helpless" */
formatkiller(tmpbuf, sizeof tmpbuf, how, FALSE);
Fprintf(rfile, "%s%cname=%s%cdeath=%s",
buf, /* (already includes separator) */
XLOG_SEP, g.plname, XLOG_SEP, tmpbuf);
if (g.multi)
Fprintf(rfile, "%cwhile=%s", XLOG_SEP,
g.multi_reason ? g.multi_reason : "helpless");
Fprintf(rfile, "%cconduct=0x%lx%cturns=%ld%cachieve=0x%lx", XLOG_SEP,
encodeconduct(), XLOG_SEP, g.moves, XLOG_SEP,
encodeachieve(FALSE));
Fprintf(rfile, "%cachieveX=%s", XLOG_SEP, encode_extended_achievements());
Fprintf(rfile, "%cconductX=%s", XLOG_SEP, encode_extended_conducts());
Fprintf(rfile, "%crealtime=%ld%cstarttime=%ld%cendtime=%ld", XLOG_SEP,
urealtime.realtime, XLOG_SEP,
timet_to_seconds(ubirthday), XLOG_SEP,
timet_to_seconds(urealtime.finish_time));
Fprintf(rfile, "%cgender0=%s%calign0=%s", XLOG_SEP,
genders[flags.initgend].filecode, XLOG_SEP,
aligns[1 - u.ualignbase[A_ORIGINAL]].filecode);
Fprintf(rfile, "%cflags=0x%lx", XLOG_SEP, encodexlogflags());
Fprintf(rfile, "%cgold=%ld", XLOG_SEP,
money_cnt(g.invent) + hidden_gold(TRUE));
Fprintf(rfile, "%cwish_cnt=%ld", XLOG_SEP, u.uconduct.wishes);
Fprintf(rfile, "%carti_wish_cnt=%ld", XLOG_SEP, u.uconduct.wisharti);
Fprintf(rfile, "%cbones=%ld", XLOG_SEP, u.uroleplay.numbones);
Fprintf(rfile, "\n");
#undef XLOG_SEP
}
static long
encodexlogflags(void)
{
long e = 0L;
if (wizard)
e |= 1L << 0;
if (discover)
e |= 1L << 1;
if (!u.uroleplay.numbones)
e |= 1L << 2;
return e;
}
static long
encodeconduct(void)
{
long e = 0L;
if (!u.uconduct.food)
e |= 1L << 0;
if (!u.uconduct.unvegan)
e |= 1L << 1;
if (!u.uconduct.unvegetarian)
e |= 1L << 2;
if (!u.uconduct.gnostic)
e |= 1L << 3;
if (!u.uconduct.weaphit)
e |= 1L << 4;
if (!u.uconduct.killer)
e |= 1L << 5;
if (!u.uconduct.literate)
e |= 1L << 6;
if (!u.uconduct.polypiles)
e |= 1L << 7;
if (!u.uconduct.polyselfs)
e |= 1L << 8;
if (!u.uconduct.wishes)
e |= 1L << 9;
if (!u.uconduct.wisharti)
e |= 1L << 10;
if (!num_genocides())
e |= 1L << 11;
/* one bit isn't really adequate for sokoban conduct:
reporting "obeyed sokoban rules" is misleading if sokoban wasn't
completed or at least attempted; however, suppressing that when
sokoban was never entered, as we do here, risks reporting
"violated sokoban rules" when no such thing occured; this can
be disambiguated in xlogfile post-processors by testing the
entered-sokoban bit in the 'achieve' field */
if (!u.uconduct.sokocheat && sokoban_in_play())
e |= 1L << 12;
return e;
}
static long
encodeachieve(
boolean secondlong) /* False: handle achievements 1..31, True: 32..62 */
{
int i, achidx, offset;
long r = 0L;
/*
* 32: portable limit for 'long'.
* Force 32 even on configurations that are using 64 bit longs.
*
* We use signed long and limit ourselves to 31 bits since tools
* that post-process xlogfile might not be able to cope with
* 'unsigned long'.
*/
offset = secondlong ? (32 - 1) : 0;
for (i = 0; u.uachieved[i]; ++i) {
achidx = u.uachieved[i] - offset;
if (achidx > 0 && achidx < 32) /* value 1..31 sets bit 0..30 */
r |= 1L << (achidx - 1);
}
return r;
}
/* add the achievement or conduct comma-separated to string */
static void
add_achieveX(char* buf, const char* achievement, boolean condition)
{
if (condition) {
if (buf[0] != '\0') {
Strcat(buf, ",");
}
Strcat(buf, achievement);
}
}
static char *
encode_extended_achievements(void)
{
static char buf[N_ACH * 40];
char rnkbuf[40];
const char *achievement = NULL;
int i, achidx, absidx;
buf[0] = '\0';
for (i = 0; u.uachieved[i]; i++) {
achidx = u.uachieved[i];
absidx = abs(achidx);
switch (absidx) {
case ACH_UWIN:
achievement = "ascended";
break;
case ACH_ASTR:
achievement = "entered_astral_plane";
break;
case ACH_ENDG:
achievement = "entered_elemental_planes";
break;
case ACH_AMUL:
achievement = "obtained_the_amulet_of_yendor";
break;
case ACH_INVK:
achievement = "performed_the_invocation_ritual";
break;
case ACH_BOOK:
achievement = "obtained_the_book_of_the_dead";
break;
case ACH_BELL:
achievement = "obtained_the_bell_of_opening";
break;
case ACH_CNDL:
achievement = "obtained_the_candelabrum_of_invocation";
break;
case ACH_HELL:
achievement = "entered_gehennom";
break;
case ACH_MEDU:
achievement = "defeated_medusa";
break;
case ACH_MINE_PRIZE:
achievement = "obtained_the_luckstone_from_the_mines";
break;
case ACH_SOKO_PRIZE:
achievement = "obtained_the_sokoban_prize";
break;
case ACH_ORCL:
achievement = "consulted_the_oracle";
break;
case ACH_NOVL:
achievement = "read_a_discworld_novel";
break;
case ACH_MINE:
achievement = "entered_the_gnomish_mines";
break;
case ACH_TOWN:
achievement = "entered_mine_town";
break;
case ACH_SHOP:
achievement = "entered_a_shop";
break;
case ACH_TMPL:
achievement = "entered_a_temple";
break;
case ACH_SOKO:
achievement = "entered_sokoban";
break;
case ACH_BGRM:
achievement = "entered_bigroom";
break;
/* rank 0 is the starting condition, not an achievement; 8 is Xp 30 */
case ACH_RNK1: case ACH_RNK2: case ACH_RNK3: case ACH_RNK4:
case ACH_RNK5: case ACH_RNK6: case ACH_RNK7: case ACH_RNK8:
Sprintf(rnkbuf, "attained_the_rank_of_%s",
rank_of(rank_to_xlev(absidx - (ACH_RNK1 - 1)),
Role_switch, (achidx < 0) ? TRUE : FALSE));
strNsubst(rnkbuf, " ", "_", 0); /* replace every ' ' with '_' */
achievement = lcase(rnkbuf);
break;
default:
continue;
}
add_achieveX(buf, achievement, TRUE);
}
return buf;
}
static char *
encode_extended_conducts(void)
{
static char buf[BUFSZ];
buf[0] = '\0';
add_achieveX(buf, "foodless", !u.uconduct.food);
add_achieveX(buf, "vegan", !u.uconduct.unvegan);
add_achieveX(buf, "vegetarian", !u.uconduct.unvegetarian);
add_achieveX(buf, "atheist", !u.uconduct.gnostic);
add_achieveX(buf, "weaponless", !u.uconduct.weaphit);
add_achieveX(buf, "pacifist", !u.uconduct.killer);
add_achieveX(buf, "illiterate", !u.uconduct.literate);
add_achieveX(buf, "polyless", !u.uconduct.polypiles);
add_achieveX(buf, "polyselfless", !u.uconduct.polyselfs);
add_achieveX(buf, "wishless", !u.uconduct.wishes);
add_achieveX(buf, "artiwishless", !u.uconduct.wisharti);
add_achieveX(buf, "genocideless", !num_genocides());
if (sokoban_in_play())
add_achieveX(buf, "sokoban", !u.uconduct.sokocheat);
add_achieveX(buf, "blind", u.uroleplay.blind);
add_achieveX(buf, "nudist", u.uroleplay.nudist);
add_achieveX(buf, "bonesless", !flags.bones);
return buf;
}
#endif /* XLOGFILE */
static void
free_ttlist(struct toptenentry* tt)
{
struct toptenentry *ttnext;
while (tt->points > 0) {
ttnext = tt->tt_next;
dealloc_ttentry(tt);
tt = ttnext;
}
dealloc_ttentry(tt);
}
void
topten(int how, time_t when)
{
register struct toptenentry *t0, *tprev;
struct toptenentry *t1;
FILE *rfile;
#ifdef LOGFILE
FILE *lfile;
#endif
#ifdef XLOGFILE
FILE *xlfile;
#endif
int uid = getuid();
int rank, rank0 = -1, rank1 = 0;
int occ_cnt = sysopt.persmax;
int flg = 0;
boolean t0_used, skip_scores;
#ifdef UPDATE_RECORD_IN_PLACE
final_fpos = 0L;
#endif
/* If we are in the midst of a panic, cut out topten entirely.
* topten uses alloc() several times, which will lead to
* problems if the panic was the result of an alloc() failure.
*/
if (g.program_state.panicking)
return;
if (iflags.toptenwin) {
g.toptenwin = create_nhwindow(NHW_TEXT);
}
#if defined(UNIX) || defined(VMS) || defined(__EMX__)
#define HUP if (!g.program_state.done_hup)
#else
#define HUP
#endif
#ifdef TOS
restore_colors(); /* make sure the screen is black on white */
#endif
/* create a new 'topten' entry */
t0_used = FALSE;
t0 = newttentry();
*t0 = zerott;
t0->ver_major = VERSION_MAJOR;
t0->ver_minor = VERSION_MINOR;
t0->patchlevel = PATCHLEVEL;
t0->points = u.urexp;
t0->deathdnum = u.uz.dnum;
/* deepest_lev_reached() is in terms of depth(), and reporting the
* deepest level reached in the dungeon death occurred in doesn't
* seem right, so we have to report the death level in depth() terms
* as well (which also seems reasonable since that's all the player
* sees on the screen anyway)
*/
t0->deathlev = observable_depth(&u.uz);
t0->maxlvl = deepest_lev_reached(TRUE);
t0->hp = u.uhp;
t0->maxhp = u.uhpmax;
t0->deaths = u.umortality;
t0->uid = uid;
copynchars(t0->plrole, g.urole.filecode, ROLESZ);
copynchars(t0->plrace, g.urace.filecode, ROLESZ);
copynchars(t0->plgend, genders[flags.female].filecode, ROLESZ);
copynchars(t0->plalign, aligns[1 - u.ualign.type].filecode, ROLESZ);
copynchars(t0->name, g.plname, NAMSZ);
formatkiller(t0->death, sizeof t0->death, how, TRUE);
t0->birthdate = yyyymmdd(ubirthday);
t0->deathdate = yyyymmdd(when);
t0->tt_next = 0;
#ifdef UPDATE_RECORD_IN_PLACE
t0->fpos = -1L;
#endif
#ifdef LOGFILE /* used for debugging (who dies of what, where) */
if (lock_file(LOGFILE, SCOREPREFIX, 10)) {
if (!(lfile = fopen_datafile(LOGFILE, "a", SCOREPREFIX))) {
HUP raw_print("Cannot open log file!");
} else {
writeentry(lfile, t0);
(void) fclose(lfile);
}
unlock_file(LOGFILE);
}
#endif /* LOGFILE */
#ifdef XLOGFILE
if (lock_file(XLOGFILE, SCOREPREFIX, 10)) {
if (!(xlfile = fopen_datafile(XLOGFILE, "a", SCOREPREFIX))) {
HUP raw_print("Cannot open extended log file!");
} else {
writexlentry(xlfile, t0, how);
(void) fclose(xlfile);
}
unlock_file(XLOGFILE);
}
#endif /* XLOGFILE */
if (wizard || discover) {
if (how != PANICKED)
HUP {
char pbuf[BUFSZ];
topten_print("");
Sprintf(pbuf,
"Since you were in %s mode, the score list will not be checked.",
wizard ? "wizard" : "discover");
topten_print(pbuf);
}
goto showwin;
}
if (!lock_file(RECORD, SCOREPREFIX, 60))
goto destroywin;
#ifdef UPDATE_RECORD_IN_PLACE
rfile = fopen_datafile(RECORD, "r+", SCOREPREFIX);
#else
rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
#endif
if (!rfile) {
HUP raw_print("Cannot open record file!");
unlock_file(RECORD);
goto destroywin;
}
HUP topten_print("");
/* assure minimum number of points */
if (t0->points < sysopt.pointsmin)
t0->points = 0;
t1 = tt_head = newttentry();
tprev = 0;
/* rank0: -1 undefined, 0 not_on_list, n n_th on list */
for (rank = 1; ; ) {
readentry(rfile, t1);
if (t1->points < sysopt.pointsmin)
t1->points = 0;
if (rank0 < 0 && t1->points < t0->points) {
rank0 = rank++;
if (tprev == 0)
tt_head = t0;
else
tprev->tt_next = t0;
t0->tt_next = t1;
#ifdef UPDATE_RECORD_IN_PLACE
t0->fpos = t1->fpos; /* insert here */
#endif
t0_used = TRUE;
occ_cnt--;
flg++; /* ask for a rewrite */
} else
tprev = t1;
if (t1->points == 0)
break;
if ((sysopt.pers_is_uid ? t1->uid == t0->uid
: strncmp(t1->name, t0->name, NAMSZ) == 0)
&& !strncmp(t1->plrole, t0->plrole, ROLESZ) && --occ_cnt <= 0) {
if (rank0 < 0) {
rank0 = 0;
rank1 = rank;
HUP {
char pbuf[BUFSZ];
Sprintf(pbuf,
"You didn't beat your previous score of %ld points.",
t1->points);
topten_print(pbuf);
topten_print("");
}
}
if (occ_cnt < 0) {
flg++;
continue;
}
}
if (rank <= sysopt.entrymax) {
t1->tt_next = newttentry();
t1 = t1->tt_next;
rank++;
}
if (rank > sysopt.entrymax) {
t1->points = 0;
break;
}
}
if (flg) { /* rewrite record file */
#ifdef UPDATE_RECORD_IN_PLACE
(void) fseek(rfile, (t0->fpos >= 0) ? t0->fpos : final_fpos, SEEK_SET);
#else
(void) fclose(rfile);
if (!(rfile = fopen_datafile(RECORD, "w", SCOREPREFIX))) {
HUP raw_print("Cannot write record file");
unlock_file(RECORD);
free_ttlist(tt_head);
goto destroywin;
}
#endif /* UPDATE_RECORD_IN_PLACE */
if (!done_stopprint)
if (rank0 > 0) {
if (rank0 <= 10) {
topten_print("You made the top ten list!");
} else {
char pbuf[BUFSZ];
Sprintf(pbuf,
"You reached the %d%s place on the top %d list.",
rank0, ordin(rank0), sysopt.entrymax);
topten_print(pbuf);
}
topten_print("");
}
}
skip_scores = !flags.end_top && !flags.end_around && !flags.end_own;
if (rank0 == 0)
rank0 = rank1;
if (rank0 <= 0)
rank0 = rank;
if (!skip_scores && !done_stopprint)
outheader();
for (t1 = tt_head, rank = 1; t1->points != 0; t1 = t1->tt_next, ++rank) {
if (flg
#ifdef UPDATE_RECORD_IN_PLACE
&& rank >= rank0
#endif
)
writeentry(rfile, t1);
if (skip_scores || done_stopprint)
continue;
if (rank <= flags.end_top
|| (rank >= rank0 - flags.end_around
&& rank <= rank0 + flags.end_around)
|| (flags.end_own && (sysopt.pers_is_uid
? t1->uid == t0->uid
: !strncmp(t1->name, t0->name, NAMSZ)))) {
if (rank == rank0 - flags.end_around
&& rank0 > flags.end_top + flags.end_around + 1
&& !flags.end_own)
topten_print("");
if (rank != rank0) {
outentry(rank, t1, FALSE);
} else if (!rank1) {
outentry(rank, t1, TRUE);
} else {
outentry(rank, t1, TRUE);
outentry(0, t0, TRUE);
}
}
}
if (rank0 >= rank)
if (!skip_scores && !done_stopprint)
outentry(0, t0, TRUE);
#ifdef UPDATE_RECORD_IN_PLACE
if (flg) {
#ifdef TRUNCATE_FILE
/* if a reasonable way to truncate a file exists, use it */
truncate_file(rfile);
#else
/* use sentinel record rather than relying on truncation */
*t1 = zerott;
t1->points = 0L; /* [redundant] terminates file when read back in */
t1->plrole[0] = t1->plrace[0] = t1->plgend[0] = t1->plalign[0] = '-';
t1->birthdate = t1->deathdate = yyyymmdd((time_t) 0L);
Strcpy(t1->name, "@");
Strcpy(t1->death, "<eod>\n"); /* end of data */
writeentry(rfile, t1);
/* note: there might be junk (if file has shrunk due to shorter
entries supplanting longer ones) after this dummy entry, but
reading and/or updating will ignore it */
(void) fflush(rfile);
#endif /* TRUNCATE_FILE */
}
#endif /* UPDATE_RECORD_IN_PLACE */
(void) fclose(rfile);
unlock_file(RECORD);
free_ttlist(tt_head);
showwin:
if (!done_stopprint) {
if (iflags.toptenwin) {
display_nhwindow(g.toptenwin, TRUE);
} else {
/* when not a window, we need something comparable to more()
but can't use it directly because we aren't dealing with
the message window */
;
}
}
destroywin:
if (!t0_used)
dealloc_ttentry(t0);
if (iflags.toptenwin) {
destroy_nhwindow(g.toptenwin);
g.toptenwin = WIN_ERR;
}
}
static void
outheader(void)
{
char linebuf[BUFSZ];
register char *bp;
Strcpy(linebuf, " No Points Name");
bp = eos(linebuf);
while (bp < linebuf + COLNO - 9)
*bp++ = ' ';
Strcpy(bp, "Hp [max]");
topten_print(linebuf);
}
DISABLE_WARNING_FORMAT_NONLITERAL
/* so>0: standout line; so=0: ordinary line */
static void
outentry(int rank, struct toptenentry* t1, boolean so)
{
boolean second_line = TRUE;
char linebuf[BUFSZ];
char *bp, hpbuf[24], linebuf3[BUFSZ];
int hppos, lngr;
linebuf[0] = '\0';
if (rank)
Sprintf(eos(linebuf), "%3d", rank);
else
Strcat(linebuf, " ");
Sprintf(eos(linebuf), " %10ld %.10s", t1->points ? t1->points : u.urexp,
t1->name);
Sprintf(eos(linebuf), "-%s", t1->plrole);
if (t1->plrace[0] != '?')
Sprintf(eos(linebuf), "-%s", t1->plrace);
/* Printing of gender and alignment is intentional. It has been
* part of the NetHack Geek Code, and illustrates a proper way to
* specify a character from the command line.
*/
Sprintf(eos(linebuf), "-%s", t1->plgend);
if (t1->plalign[0] != '?')
Sprintf(eos(linebuf), "-%s ", t1->plalign);
else
Strcat(linebuf, " ");
if (!strncmp("escaped", t1->death, 7)) {
Sprintf(eos(linebuf), "escaped the dungeon %s[max level %d]",
!strncmp(" (", t1->death + 7, 2) ? t1->death + 7 + 2 : "",
t1->maxlvl);
/* fixup for closing paren in "escaped... with...Amulet)[max..." */
if ((bp = index(linebuf, ')')) != 0)
*bp = (t1->deathdnum == astral_level.dnum) ? '\0' : ' ';
second_line = FALSE;
} else if (!strncmp("ascended", t1->death, 8)) {
Sprintf(eos(linebuf), "ascended to demigod%s-hood",
(t1->plgend[0] == 'F') ? "dess" : "");
second_line = FALSE;
} else {
if (!strncmp(t1->death, "quit", 4)) {
Strcat(linebuf, "quit");
second_line = FALSE;
} else if (!strncmp(t1->death, "died of st", 10)) {
Strcat(linebuf, "starved to death");
second_line = FALSE;
} else if (!strncmp(t1->death, "choked", 6)) {
Sprintf(eos(linebuf), "choked on h%s food",
(t1->plgend[0] == 'F') ? "er" : "is");
} else if (!strncmp(t1->death, "poisoned", 8)) {
Strcat(linebuf, "was poisoned");
} else if (!strncmp(t1->death, "crushed", 7)) {
Strcat(linebuf, "was crushed to death");
} else if (!strncmp(t1->death, "petrified by ", 13)) {
Strcat(linebuf, "turned to stone");
} else
Strcat(linebuf, "died");
if (t1->deathdnum == astral_level.dnum) {
const char *arg, *fmt = " on the Plane of %s";
switch (t1->deathlev) {
case -5:
fmt = " on the %s Plane";
arg = "Astral";
break;
case -4:
arg = "Water";
break;
case -3:
arg = "Fire";
break;
case -2:
arg = "Air";
break;
case -1:
arg = "Earth";
break;
default:
arg = "Void";
break;
}
Sprintf(eos(linebuf), fmt, arg);
} else {
Sprintf(eos(linebuf), " in %s", g.dungeons[t1->deathdnum].dname);
if (t1->deathdnum != knox_level.dnum)
Sprintf(eos(linebuf), " on level %d", t1->deathlev);
if (t1->deathlev != t1->maxlvl)
Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
}
/* kludge for "quit while already on Charon's boat" */
if (!strncmp(t1->death, "quit ", 5))
Strcat(linebuf, t1->death + 4);
}
Strcat(linebuf, ".");
/* Quit, starved, ascended, and escaped contain no second line */
if (second_line)
Sprintf(eos(linebuf), " %c%s.", highc(*(t1->death)), t1->death + 1);
lngr = (int) strlen(linebuf);
if (t1->hp <= 0)
hpbuf[0] = '-', hpbuf[1] = '\0';
else
Sprintf(hpbuf, "%d", t1->hp);
/* beginning of hp column after padding (not actually padded yet) */
hppos = COLNO - (sizeof(" Hp [max]") - 1); /* sizeof(str) includes \0 */
while (lngr >= hppos) {
for (bp = eos(linebuf); !(*bp == ' ' && (bp - linebuf < hppos)); bp--)
;
/* special case: word is too long, wrap in the middle */
if (linebuf + 15 >= bp)
bp = linebuf + hppos - 1;
/* special case: if about to wrap in the middle of maximum
dungeon depth reached, wrap in front of it instead */
if (bp > linebuf + 5 && !strncmp(bp - 5, " [max", 5))
bp -= 5;
if (*bp != ' ')
Strcpy(linebuf3, bp);
else
Strcpy(linebuf3, bp + 1);
*bp = '\0';
if (so) {
while (bp < linebuf + (COLNO - 1))
*bp++ = ' ';
*bp = '\0';
topten_print_bold(linebuf);
} else
topten_print(linebuf);
Snprintf(linebuf, sizeof(linebuf), "%15s %s", "", linebuf3);
lngr = strlen(linebuf);
}
/* beginning of hp column not including padding */
hppos = COLNO - 7 - (int) strlen(hpbuf);
bp = eos(linebuf);
if (bp <= linebuf + hppos) {
/* pad any necessary blanks to the hit point entry */
while (bp < linebuf + hppos)
*bp++ = ' ';
Strcpy(bp, hpbuf);
Sprintf(eos(bp), " %s[%d]",
(t1->maxhp < 10) ? " " : (t1->maxhp < 100) ? " " : "",
t1->maxhp);
}
if (so) {
bp = eos(linebuf);
while (bp < linebuf + (COLNO - 1))
*bp++ = ' ';
*bp = '\0';
topten_print_bold(linebuf);
} else
topten_print(linebuf);
}
RESTORE_WARNING_FORMAT_NONLITERAL
static int
score_wanted(
boolean current_ver,
int rank,
struct toptenentry *t1,
int playerct,
const char **players,
int uid)
{
int i;
if (current_ver
&& (t1->ver_major != VERSION_MAJOR || t1->ver_minor != VERSION_MINOR
|| t1->patchlevel != PATCHLEVEL))
return 0;
if (sysopt.pers_is_uid && !playerct && t1->uid == uid)
return 1;
for (i = 0; i < playerct; i++) {
if (players[i][0] == '-' && index("pr", players[i][1])
&& players[i][2] == 0 && i + 1 < playerct) {
const char *arg = players[i + 1];
if ((players[i][1] == 'p'
&& str2role(arg) == str2role(t1->plrole))
|| (players[i][1] == 'r'
&& str2race(arg) == str2race(t1->plrace)))
return 1;
i++;
} else if (strcmp(players[i], "all") == 0
|| strncmp(t1->name, players[i], NAMSZ) == 0
|| (players[i][0] == '-' && players[i][1] == t1->plrole[0]
&& players[i][2] == 0)
|| (digit(players[i][0]) && rank <= atoi(players[i])))
return 1;
}
return 0;
}
/*
* print selected parts of score list.
* argc >= 2, with argv[0] untrustworthy (directory names, et al.),
* and argv[1] starting with "-s".
* caveat: some shells might allow argv elements to be arbitrarily long.
*/
void
prscore(int argc, char **argv)
{
const char **players;
int playerct, rank;
boolean current_ver = TRUE, init_done = FALSE;
register struct toptenentry *t1;
FILE *rfile;
boolean match_found = FALSE;
register int i;
char pbuf[BUFSZ];
int uid = -1;
const char *player0;
if (argc < 2 || strncmp(argv[1], "-s", 2)) {
raw_printf("prscore: bad arguments (%d)", argc);
return;
}
rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
if (!rfile) {
raw_print("Cannot open record file!");
return;
}
#ifdef AMIGA
{
extern winid amii_rawprwin;
init_nhwindows(&argc, argv);
amii_rawprwin = create_nhwindow(NHW_TEXT);
}
#endif
/* If the score list isn't after a game, we never went through
* initialization. */
if (wiz1_level.dlevel == 0) {
dlb_init();
init_dungeons();
init_done = TRUE;
}
if (!argv[1][2]) { /* plain "-s" */
argc--;
argv++;
} else
argv[1] += 2;
if (argc > 1 && !strcmp(argv[1], "-v")) {
current_ver = FALSE;
argc--;
argv++;
}
if (argc <= 1) {
if (sysopt.pers_is_uid) {
uid = getuid();
playerct = 0;
players = (const char **) 0;
} else {
player0 = g.plname;
if (!*player0)
#ifdef AMIGA
player0 = "all"; /* single user system */
#else
player0 = "hackplayer";
#endif
playerct = 1;
players = &player0;
}
} else {
playerct = --argc;
players = (const char **) ++argv;
}
raw_print("");
t1 = tt_head = newttentry();
for (rank = 1;; rank++) {
readentry(rfile, t1);
if (t1->points == 0)
break;
if (!match_found
&& score_wanted(current_ver, rank, t1, playerct, players, uid))
match_found = TRUE;
t1->tt_next = newttentry();
t1 = t1->tt_next;
}
(void) fclose(rfile);
if (init_done) {
free_dungeons();
dlb_cleanup();
}
if (match_found) {
outheader();
t1 = tt_head;
for (rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
if (score_wanted(current_ver, rank, t1, playerct, players, uid))
(void) outentry(rank, t1, FALSE);
}
} else {
Sprintf(pbuf, "Cannot find any %sentries for ",
current_ver ? "current " : "");
if (playerct < 1)
Strcat(pbuf, "you.");
else {
if (playerct > 1)
Strcat(pbuf, "any of ");
for (i = 0; i < playerct; i++) {
/* stop printing players if there are too many to fit */
if (strlen(pbuf) + strlen(players[i]) + 2 >= BUFSZ) {
if (strlen(pbuf) < BUFSZ - 4)
Strcat(pbuf, "...");
else
Strcpy(pbuf + strlen(pbuf) - 4, "...");
break;
}
Strcat(pbuf, players[i]);
if (i < playerct - 1) {
if (players[i][0] == '-' && index("pr", players[i][1])
&& players[i][2] == 0)
Strcat(pbuf, " ");
else
Strcat(pbuf, ":");
}
}
}
raw_print(pbuf);
raw_printf("Usage: %s -s [-v] <playertypes> [maxrank] [playernames]",
g.hname);
raw_printf("Player types are: [-p role] [-r race]");
}
free_ttlist(tt_head);
#ifdef AMIGA
{
extern winid amii_rawprwin;
display_nhwindow(amii_rawprwin, 1);
destroy_nhwindow(amii_rawprwin);
amii_rawprwin = WIN_ERR;
}
#endif
}
static int
classmon(char *plch)
{
int i;
/* Look for this role in the role table */
for (i = 0; roles[i].name.m; i++) {
if (!strncmp(plch, roles[i].filecode, ROLESZ)) {
if (roles[i].mnum != NON_PM)
return roles[i].mnum;
else
return PM_HUMAN;
}
}
/* this might be from a 3.2.x score for former Elf class */
if (!strcmp(plch, "E"))
return PM_RANGER;
impossible("What weird role is this? (%s)", plch);
return PM_HUMAN_MUMMY;
}
/*
* Get a random player name and class from the high score list,
*/
struct toptenentry *
get_rnd_toptenentry(void)
{
int rank, i;
FILE *rfile;
register struct toptenentry *tt;
static struct toptenentry tt_buf;
rfile = fopen_datafile(RECORD, "r", SCOREPREFIX);
if (!rfile) {
impossible("Cannot open record file!");
return NULL;
}
tt = &tt_buf;
rank = rnd(sysopt.tt_oname_maxrank);
pickentry:
for (i = rank; i; i--) {
readentry(rfile, tt);
if (tt->points == 0)
break;
}
if (tt->points == 0) {
if (rank > 1) {
rank = 1;
rewind(rfile);
goto pickentry;
}
tt = NULL;
}
(void) fclose(rfile);
return tt;
}
/*
* Attach random player name and class from high score list
* to an object (for statues or morgue corpses).
*/
struct obj *
tt_oname(struct obj* otmp)
{
struct toptenentry *tt;
if (!otmp)
return (struct obj *) 0;
tt = get_rnd_toptenentry();
if (!tt)
return (struct obj *) 0;
set_corpsenm(otmp, classmon(tt->plrole));
if (tt->plgend[0] == 'F')
otmp->spe = CORPSTAT_FEMALE;
else if (tt->plgend[0] == 'M')
otmp->spe = CORPSTAT_MALE;
otmp = oname(otmp, tt->name);
return otmp;
}
#ifdef NO_SCAN_BRACK
/* Lattice scanf isn't up to reading the scorefile. What */
/* follows deals with that; I admit it's ugly. (KL) */
/* Now generally available (KL) */
static void
nsb_mung_line(p)
char *p;
{
while ((p = index(p, ' ')) != 0)
*p = '|';
}
static void
nsb_unmung_line(p)
char *p;
{
while ((p = index(p, '|')) != 0)
*p = ' ';
}
#endif /* NO_SCAN_BRACK */
/*topten.c*/