gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:
The range and shape of "falls through" comments accepted are
contingent upon the level of the warning. (The default level is =3.)
-Wimplicit-fallthrough=0 disables the warning altogether.
-Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
-Wimplicit-fallthrough=2 essentially accepts any comment that contains something
that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
-Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
expressions, listed in the GCC manual. E.g., all of these are accepted:
/* Falls through. */
/* fall-thru */
/* Else falls through. */
/* FALLTHRU */
/* ... falls through ... */
etc.
-Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
expressions but is much more strict than level =3.
-Wimplicit-fallthrough=5 doesn't recognize any comments.
Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.
The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.
Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:
C compiler whitelist approach
--------------------------- -------------------------------------
C23 conforming compilers [[fallthrough]]
clang versions supporting
standards prior to
C23 __attribute__((__fallthrough__))
Microsoft Visual Studio
since VS 2022 17.4.
The warning C5262 controls
whether the implict
fallthrough is detected and
warned about with
/std:clatest. [[fallthrough]]
This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.
The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.
The gcc comment approach has also been left in place at this time.
951 lines
35 KiB
C
951 lines
35 KiB
C
/* NetHack 3.7 rumors.c $NHDT-Date: 1594370241 2020/07/10 08:37:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.56 $ */
|
|
/* 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"
|
|
|
|
/* [Note: this comment is fairly old, but still accurate for 3.1;
|
|
* it's no longer accurate for 3.7 but may still be of interest.]
|
|
* Rumors have been entirely rewritten to speed up the access. This is
|
|
* essential when working from floppies. Using fseek() the way that's done
|
|
* here means rumors following longer rumors are output more often than those
|
|
* following shorter rumors. Also, you may see the same rumor more than once
|
|
* in a particular game (although the odds are highly against it), but
|
|
* this also happens with real fortune cookies. -dgk
|
|
*/
|
|
|
|
/* 3.6
|
|
* The rumors file consists of a "do not edit" line, then a line containing
|
|
* three sets of three counts (first two in decimal, third in hexadecimal).
|
|
* The first set has the number of true rumors, the count in bytes for all
|
|
* true rumors, and the file offset to the first one. The second set has
|
|
* the same group of numbers for the false rumors. The third set has 0 for
|
|
* count, 0 for size, and the file offset for end-of-file. The offset of
|
|
* the first true rumor plus the size of the true rumors matches the offset
|
|
* of the first false rumor. Likewise, the offset of the first false rumor
|
|
* plus the size of the false rumors matches the offset for end-of-file.
|
|
*/
|
|
|
|
/* 3.1 [now obsolete for rumors but still accurate for oracles]
|
|
* The rumors file consists of a "do not edit" line, a hexadecimal number
|
|
* giving the number of bytes of useful/true rumors, followed by those
|
|
* true rumors (one per line), followed by the useless/false/misleading/cute
|
|
* rumors (also one per line). Number of bytes of untrue rumors is derived
|
|
* via fseek(EOF)+ftell().
|
|
*
|
|
* The oracles file consists of a "do not edit" comment, a decimal count N
|
|
* and set of N+1 hexadecimal fseek offsets, followed by N multiple-line
|
|
* records, separated by "---" lines. The first oracle is a special case,
|
|
* and placed there by 'makedefs'.
|
|
*/
|
|
|
|
staticfn void unpadline(char *);
|
|
staticfn void init_rumors(dlb *);
|
|
staticfn char *get_rnd_line(dlb *, char *, unsigned, int (*)(int),
|
|
long, long, unsigned);
|
|
staticfn void init_oracles(dlb *);
|
|
staticfn void others_check(const char *ftype, const char *, winid *);
|
|
staticfn void couldnt_open_file(const char *);
|
|
staticfn void init_CapMons(void);
|
|
|
|
/* used by CapitalMon(); set up by init_CapMons(), released by free_CapMons();
|
|
there's no need for these to be put into 'struct instance_globals g' */
|
|
static unsigned CapMonstCnt = 0, CapBogonCnt = 0,
|
|
CapMonSiz = 0; /* CapMonstCnt+CapBogonCnt+1 when non-zero */
|
|
static const char **CapMons = 0;
|
|
|
|
/* list of bogusmons prefixes used to indicate special monster type such as
|
|
unique or always a particular gender; see dat/bogusmon.txt */
|
|
extern const char bogon_codes[]; /* from do_name.c */
|
|
|
|
/* makedefs pads short rumors, epitaphs, engravings, and hallucinatory
|
|
monster names with trailing underscores; strip those off */
|
|
staticfn void
|
|
unpadline(char *line)
|
|
{
|
|
char *p = eos(line);
|
|
|
|
/* remove newline if still present; caller should have stripped it */
|
|
if (p > line && p[-1] == '\n')
|
|
--p;
|
|
|
|
/* remove padding */
|
|
while (p > line && p[-1] == '_')
|
|
--p;
|
|
|
|
*p = '\0';
|
|
}
|
|
|
|
DISABLE_WARNING_FORMAT_NONLITERAL
|
|
|
|
staticfn void
|
|
init_rumors(dlb *fp)
|
|
{
|
|
static const char rumors_header[] = "%d,%ld,%lx;%d,%ld,%lx;0,0,%lx\n";
|
|
int true_count, false_count; /* in file but not used here */
|
|
unsigned long eof_offset;
|
|
char line[BUFSZ];
|
|
|
|
(void) dlb_fgets(line, sizeof line, fp); /* skip "don't edit" comment */
|
|
(void) dlb_fgets(line, sizeof line, fp);
|
|
if (sscanf(line, rumors_header, &true_count, >.true_rumor_size,
|
|
>.true_rumor_start, &false_count, &gf.false_rumor_size,
|
|
&gf.false_rumor_start, &eof_offset) == 7
|
|
&& gt.true_rumor_size > 0L
|
|
&& gf.false_rumor_size > 0L) {
|
|
gt.true_rumor_end = (long) gt.true_rumor_start + gt.true_rumor_size;
|
|
/* assert( gt.true_rumor_end == false_rumor_start ); */
|
|
gf.false_rumor_end = (long) gf.false_rumor_start + gf.false_rumor_size;
|
|
/* assert( gf.false_rumor_end == eof_offset ); */
|
|
} else {
|
|
gt.true_rumor_size = -1L; /* init failed */
|
|
(void) dlb_fclose(fp);
|
|
}
|
|
}
|
|
|
|
RESTORE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* exclude_cookie is a hack used because we sometimes want to get rumors in a
|
|
* context where messages such as "You swallowed the fortune!" that refer to
|
|
* cookies should not appear. This has no effect for true rumors since none
|
|
* of them contain such references anyway.
|
|
*/
|
|
char *
|
|
getrumor(
|
|
int truth, /* 1=true, -1=false, 0=either */
|
|
char *rumor_buf,
|
|
boolean exclude_cookie)
|
|
{
|
|
dlb *rumors;
|
|
long beginning, ending;
|
|
char line[BUFSZ];
|
|
static const char *cookie_marker = "[cookie] ";
|
|
const int marklen = strlen(cookie_marker);
|
|
|
|
rumor_buf[0] = '\0';
|
|
if (gt.true_rumor_size < 0L) /* a previous try failed to open RUMORFILE */
|
|
return rumor_buf;
|
|
|
|
rumors = dlb_fopen(RUMORFILE, "r");
|
|
if (rumors) {
|
|
int count = 0;
|
|
int adjtruth;
|
|
|
|
do {
|
|
rumor_buf[0] = '\0';
|
|
if (gt.true_rumor_size == 0L) { /* if this is 1st outrumor() */
|
|
init_rumors(rumors);
|
|
if (gt.true_rumor_size < 0L) { /* init failed */
|
|
Sprintf(rumor_buf, "Error reading \"%.80s\".", RUMORFILE);
|
|
return rumor_buf;
|
|
}
|
|
}
|
|
/*
|
|
* input: 1 0 -1
|
|
* rn2 \ +1 2=T 1=T 0=F
|
|
* adj./ +0 1=T 0=F -1=F
|
|
*/
|
|
switch (adjtruth = truth + rn2(2)) {
|
|
case 2: /*(might let a bogus input arg sneak thru)*/
|
|
case 1:
|
|
beginning = (long) gt.true_rumor_start;
|
|
ending = gt.true_rumor_end;
|
|
break;
|
|
case 0: /* once here, 0 => false rather than "either"*/
|
|
case -1:
|
|
beginning = (long) gf.false_rumor_start;
|
|
ending = gf.false_rumor_end;
|
|
break;
|
|
default:
|
|
impossible("strange truth value for rumor");
|
|
return strcpy(rumor_buf, "Oops...");
|
|
}
|
|
Strcpy(rumor_buf,
|
|
get_rnd_line(rumors, line, (unsigned) sizeof line, rn2,
|
|
beginning, ending, MD_PAD_RUMORS));
|
|
} while (count++ < 50 && exclude_cookie
|
|
&& !strncmp(rumor_buf, cookie_marker, marklen));
|
|
(void) dlb_fclose(rumors);
|
|
if (count >= 50)
|
|
impossible("Can't find non-cookie rumor?");
|
|
else if (!gi.in_mklev) /* avoid exercising wisdom for graffiti */
|
|
exercise(A_WIS, (adjtruth > 0));
|
|
} else {
|
|
couldnt_open_file(RUMORFILE);
|
|
gt.true_rumor_size = -1; /* don't try to open it again */
|
|
}
|
|
if (!exclude_cookie
|
|
&& !strncmp(rumor_buf, cookie_marker, marklen)) {
|
|
/* remove cookie_marker from the string */
|
|
char *src = rumor_buf + marklen;
|
|
char *dst = rumor_buf;
|
|
for (; *src != '\0'; ++src, ++dst) {
|
|
*dst = *src;
|
|
}
|
|
*dst = '\0'; /* terminator wasn't copied */
|
|
}
|
|
return rumor_buf;
|
|
}
|
|
|
|
/* test that the true/false rumor boundaries are valid and show the first
|
|
two and very last epitaphs, engravings, and bogus monsters */
|
|
void
|
|
rumor_check(void)
|
|
{
|
|
dlb *rumors;
|
|
winid tmpwin = WIN_ERR;
|
|
char *endp, line[BUFSZ], xbuf[BUFSZ], rumor_buf[BUFSZ];
|
|
|
|
rumors = (gt.true_rumor_size >= 0) ? dlb_fopen(RUMORFILE, "r") : 0;
|
|
if (rumors) {
|
|
long ftell_rumor_start = 0L;
|
|
|
|
rumor_buf[0] = '\0';
|
|
if (gt.true_rumor_size == 0L) { /* if this is 1st outrumor() */
|
|
init_rumors(rumors);
|
|
if (gt.true_rumor_size < 0L) {
|
|
rumors = (dlb *) 0; /* init_rumors() closes it upon failure */
|
|
goto no_rumors; /* init failed */
|
|
}
|
|
}
|
|
tmpwin = create_nhwindow(NHW_TEXT);
|
|
|
|
/*
|
|
* reveal the values.
|
|
*/
|
|
Sprintf(rumor_buf,
|
|
"T start=%06ld (%06lx), end=%06ld (%06lx), size=%06ld (%06lx)",
|
|
(long) gt.true_rumor_start, gt.true_rumor_start,
|
|
gt.true_rumor_end, (unsigned long) gt.true_rumor_end,
|
|
gt.true_rumor_size,(unsigned long) gt.true_rumor_size);
|
|
putstr(tmpwin, 0, rumor_buf);
|
|
Sprintf(rumor_buf,
|
|
"F start=%06ld (%06lx), end=%06ld (%06lx), size=%06ld (%06lx)",
|
|
(long) gf.false_rumor_start, gf.false_rumor_start,
|
|
gf.false_rumor_end, (unsigned long) gf.false_rumor_end,
|
|
gf.false_rumor_size, (unsigned long) gf.false_rumor_size);
|
|
putstr(tmpwin, 0, rumor_buf);
|
|
|
|
/*
|
|
* check the first rumor (start of true rumors) by
|
|
* skipping the first two lines.
|
|
*
|
|
* Then seek to the start of the false rumors (based on
|
|
* the value read in rumors, and display it.
|
|
*/
|
|
rumor_buf[0] = '\0';
|
|
(void) dlb_fseek(rumors, (long) gt.true_rumor_start, SEEK_SET);
|
|
ftell_rumor_start = dlb_ftell(rumors);
|
|
(void) dlb_fgets(line, sizeof line, rumors);
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
Sprintf(rumor_buf, "T %06ld %s", ftell_rumor_start,
|
|
xcrypt(line, xbuf));
|
|
putstr(tmpwin, 0, rumor_buf);
|
|
/* find last true rumor */
|
|
while (dlb_fgets(line, sizeof line, rumors)
|
|
&& dlb_ftell(rumors) < gt.true_rumor_end)
|
|
continue;
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
Sprintf(rumor_buf, " %6s %s", "", xcrypt(line, xbuf));
|
|
putstr(tmpwin, 0, rumor_buf);
|
|
|
|
rumor_buf[0] = '\0';
|
|
(void) dlb_fseek(rumors, (long) gf.false_rumor_start, SEEK_SET);
|
|
ftell_rumor_start = dlb_ftell(rumors);
|
|
(void) dlb_fgets(line, sizeof line, rumors);
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
Sprintf(rumor_buf, "F %06ld %s", ftell_rumor_start,
|
|
xcrypt(line, xbuf));
|
|
putstr(tmpwin, 0, rumor_buf);
|
|
/* find last false rumor */
|
|
while (dlb_fgets(line, sizeof line, rumors)
|
|
&& dlb_ftell(rumors) < gf.false_rumor_end)
|
|
continue;
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
Sprintf(rumor_buf, " %6s %s", "", xcrypt(line, xbuf));
|
|
putstr(tmpwin, 0, rumor_buf);
|
|
|
|
(void) dlb_fclose(rumors);
|
|
|
|
/* if a previous attempt couldn't open file or rejected its contents,
|
|
we didn't bother trying again this time */
|
|
} else if (gt.true_rumor_size < 0L) {
|
|
no_rumors: /* file could be opened but init_rumors() didn't like it */
|
|
pline("rumors not accessible.");
|
|
/* engravings, epitaphs, and bogus monsters will still be shown,
|
|
and in tmpwin rather than via additional pline() calls */
|
|
display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */
|
|
|
|
/* first attempt to open file has just failed */
|
|
} else {
|
|
couldnt_open_file(RUMORFILE);
|
|
gt.true_rumor_size = -1; /* don't try to open it again */
|
|
}
|
|
|
|
/* initial implementation of default epitaph/engraving/bogusmon
|
|
contained an error; check those along with rumors */
|
|
others_check("Engravings:", ENGRAVEFILE, &tmpwin);
|
|
others_check("Epitaphs:", EPITAPHFILE, &tmpwin);
|
|
others_check("Bogus monsters:", BOGUSMONFILE, &tmpwin);
|
|
|
|
if (tmpwin != WIN_ERR) {
|
|
display_nhwindow(tmpwin, TRUE);
|
|
destroy_nhwindow(tmpwin);
|
|
}
|
|
}
|
|
|
|
DISABLE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* 3.7: augments rumors_check(); test 'engrave' or 'epitaph' or 'bogusmon' */
|
|
staticfn void
|
|
others_check(
|
|
const char *ftype, /* header: "{Engravings|Epitaphs|Bogus monsters}:" */
|
|
const char *fname, /* filename: {ENGRAVEFILE|EPITAPHFILE|BOGUSMONFILE} */
|
|
winid *winptr) /* text window for output; created here if necessary */
|
|
{
|
|
static const char errfmt[] = "others_check(\"%s\"): %s";
|
|
dlb *fh;
|
|
char line[BUFSZ], xbuf[BUFSZ], *endp;
|
|
winid tmpwin = *winptr;
|
|
int entrycount = 0;
|
|
|
|
fh = dlb_fopen(fname, "r");
|
|
if (fh) {
|
|
if (tmpwin == WIN_ERR) {
|
|
*winptr = tmpwin = create_nhwindow(NHW_TEXT);
|
|
if (tmpwin == WIN_ERR) {
|
|
/* should panic, but won't for wizard mode check operation */
|
|
impossible(errfmt, fname, "can't create temporary window");
|
|
goto closeit;
|
|
}
|
|
}
|
|
putstr(tmpwin, 0, "");
|
|
putstr(tmpwin, 0, ftype);
|
|
/* "don't edit" comment */
|
|
*line = '\0';
|
|
if (!dlb_fgets(line, sizeof line, fh)) {
|
|
Sprintf(xbuf, errfmt, fname, "error; can't read comment line");
|
|
putstr(tmpwin, 0, xbuf);
|
|
goto closeit;
|
|
}
|
|
if (*line != '#') {
|
|
Sprintf(xbuf, errfmt, fname,
|
|
"malformed; first line is not a comment line:");
|
|
putstr(tmpwin, 0, xbuf);
|
|
/* show the bad line; we don't know whether it has been
|
|
encrypted via xcrypt() so show it both ways */
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
putstr(tmpwin, 0, "- first line, as is");
|
|
putstr(tmpwin, 0, line);
|
|
putstr(tmpwin, 0, "- xcrypt of first line");
|
|
putstr(tmpwin, 0, xcrypt(line, xbuf));
|
|
goto closeit;
|
|
}
|
|
/* first line; should be default one inserted by makedefs when
|
|
building the file but we don't have the expected value so
|
|
can only require a line to exist */
|
|
*line = '\0';
|
|
if (!dlb_fgets(line, sizeof line, fh) || *line == '\n') {
|
|
Sprintf(xbuf, errfmt, fname,
|
|
!*line ? "can't read first non-comment line"
|
|
: "first non-comment line is empty");
|
|
putstr(tmpwin, 0, xbuf);
|
|
goto closeit;
|
|
}
|
|
++entrycount;
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
putstr(tmpwin, 0, xcrypt(line, xbuf));
|
|
if (!dlb_fgets(line, sizeof line, fh)) {
|
|
putstr(tmpwin, 0, "(no second entry)");
|
|
} else {
|
|
++entrycount;
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
putstr(tmpwin, 0, xcrypt(line, xbuf));
|
|
while (dlb_fgets(line, sizeof line, fh)) {
|
|
++entrycount;
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
(void) xcrypt(line, xbuf);
|
|
}
|
|
/* count will be 2 if the default entry and the first ordinary
|
|
entry are the only ones present (if either of those were
|
|
missing, we wouldn't have gotten here...) */
|
|
if (entrycount == 2) {
|
|
putstr(tmpwin, 0, "(only two entries)");
|
|
} else {
|
|
/* showing an ellipsis avoids ambiguity about whether
|
|
there are other lines; doing so three times (once for
|
|
each file) results in total output being 24 lines,
|
|
forcing a --More-- prompt if using a 24 line screen;
|
|
displaying 23 lines and --More-- followed by second
|
|
page with 1 line doesn't look very good but isn't
|
|
incorrect, and taller screens where that won't be an
|
|
issue are more common than 24 line terminals nowadays */
|
|
if (entrycount > 3)
|
|
putstr(tmpwin, 0, " ...");
|
|
putstr(tmpwin, 0, xbuf); /* already decrypted */
|
|
}
|
|
}
|
|
|
|
closeit:
|
|
(void) dlb_fclose(fh);
|
|
} else {
|
|
/* since this comes out via impossible(), it won't be integrated
|
|
with the text window of values, but it shouldn't ever happen
|
|
so we won't waste effort integrating it */
|
|
couldnt_open_file(fname);
|
|
}
|
|
}
|
|
|
|
RESTORE_WARNING_FORMAT_NONLITERAL
|
|
|
|
/* load one randomly chosen line from a section of a file; undoes
|
|
decryption and strips trailing underscore padding and final newline;
|
|
if padlength is non-zero, every line is expected to be at least that
|
|
long and every line in the file will have an equal chance of being
|
|
chosen; however, if padlength is 0, lines following long lines are
|
|
more likely than average to be picked, and lines after short lines
|
|
are less likely */
|
|
staticfn char *
|
|
get_rnd_line(
|
|
dlb *fh, /* already opened file */
|
|
char *buf, /* output buffer */
|
|
unsigned bufsiz, /* (unsigned) sizeof buf */
|
|
int (*rng)(int), /* random number routine; rn2(N) or similar, 0..N-1 */
|
|
long startpos, /* location in file of first line of interest */
|
|
long endpos, /* location one byte past last line of interest;
|
|
* if 0, end-of-file will be used */
|
|
unsigned padlength) /* expected line length; 0 if no expectations */
|
|
{
|
|
char *newl, *xbufp, xbuf[BUFSZ];
|
|
long filechunksize, chunkoffset;
|
|
int trylimit;
|
|
|
|
*buf = '\0';
|
|
if (!endpos) {
|
|
(void) dlb_fseek(fh, 0L, SEEK_END);
|
|
endpos = dlb_ftell(fh);
|
|
}
|
|
filechunksize = endpos - startpos;
|
|
|
|
/* might be zero (only if file is empty); should complain in that
|
|
case but it could happen over and over, also the suggestion
|
|
that save and restore might fix the problem wouldn't be useful */
|
|
if (filechunksize < 1L)
|
|
return buf;
|
|
/* 'rumors' is about 3/4 of the way to the limit on a 16-bit config
|
|
for the whole, roughly 3/8 of the way for either half; all active
|
|
configurations these days are at least 32-bits anyway */
|
|
nhassert(filechunksize <= INT_MAX); /* essential for rn2() */
|
|
|
|
/*
|
|
* Position randomly which will probably be in the middle of a line.
|
|
* (Occasionally by chance it will happen to be at the very start of
|
|
* a line, but we'll have no way of knowing that so have to behave
|
|
* as if it were positioned in the middle.)
|
|
* Read the rest of that line, then use the next one. If there's no
|
|
* next line (ie, end of file), go back to beginning and use first.
|
|
*
|
|
* When short lines have been padded to length N, only accept long
|
|
* lines if we land within last N+1 characters (+1 is for newline
|
|
* which hasn't been stripped away yet), effectively shortening
|
|
* them to normal length. That yields even selection distribution.
|
|
*/
|
|
for (trylimit = 10; trylimit > 0; --trylimit) {
|
|
chunkoffset = (long) (*rng)((int) filechunksize);
|
|
(void) dlb_fseek(fh, startpos + chunkoffset, SEEK_SET);
|
|
(void) dlb_fgets(buf, bufsiz, fh);
|
|
/* if padlength is 0, accept any position; when non-zero,
|
|
padlength does not count the newline but strlen(buf) does */
|
|
if (!padlength || (unsigned) strlen(buf) <= padlength + 1)
|
|
break;
|
|
}
|
|
/* use next line; for rumors, caller takes care of whether startpos
|
|
and endpos cover just true rumors or just false rumors; reaching
|
|
endpos is equivalent to end-of-file in order to avoid using the
|
|
first false rumor if fseek for a true one lands within the last one */
|
|
if (dlb_ftell(fh) >= endpos || !dlb_fgets(buf, bufsiz, fh)) {
|
|
/* assume failure is due to end-of-file; go back to start */
|
|
(void) dlb_fseek(fh, startpos, SEEK_SET);
|
|
(void) dlb_fgets(buf, bufsiz, fh);
|
|
}
|
|
if ((newl = strchr(buf, '\n')) != 0)
|
|
*newl = '\0';
|
|
/* decrypt line; make sure that our intermediate buffer is big enough */
|
|
xbufp = (strlen(buf) <= sizeof xbuf - 1) ? &xbuf[0]
|
|
: (char *) alloc((unsigned) strlen(buf) + 1);
|
|
Strcpy(buf, xcrypt(buf, xbufp));
|
|
if (xbufp != &xbuf[0])
|
|
free((genericptr_t) xbufp);
|
|
/* strip padding that makedefs adds to short lines */
|
|
if (padlength)
|
|
unpadline(buf);
|
|
return buf;
|
|
}
|
|
|
|
/* Gets a random line of text from file 'fname', and returns it.
|
|
rng is the random number generator to use, and should act like rn2 does. */
|
|
char *
|
|
get_rnd_text(
|
|
const char *fname,
|
|
char *buf,
|
|
int (*rng)(int),
|
|
unsigned padlength)
|
|
{
|
|
dlb *fh = dlb_fopen(fname, "r");
|
|
|
|
buf[0] = '\0';
|
|
if (fh) {
|
|
long starttxt = 0L;
|
|
char line[BUFSZ];
|
|
|
|
/* skip "don't edit" comment */
|
|
(void) dlb_fgets(line, sizeof line, fh);
|
|
/* obtain current file position */
|
|
(void) dlb_fseek(fh, 0L, SEEK_CUR);
|
|
starttxt = dlb_ftell(fh);
|
|
|
|
/* get a randomly chosen line; it comes back decrypted and unpadded */
|
|
Strcpy(buf, get_rnd_line(fh, line, (unsigned) sizeof line, rng,
|
|
starttxt, 0L, padlength));
|
|
(void) dlb_fclose(fh);
|
|
} else {
|
|
couldnt_open_file(fname);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
void
|
|
outrumor(
|
|
int truth, /* 1=true, -1=false, 0=either */
|
|
int mechanism)
|
|
{
|
|
static const char fortune_msg[] =
|
|
"This cookie has a scrap of paper inside.";
|
|
const char *line;
|
|
char buf[BUFSZ];
|
|
boolean reading = (mechanism == BY_COOKIE || mechanism == BY_PAPER);
|
|
|
|
if (reading) {
|
|
/* deal with various things that prevent reading */
|
|
if (is_fainted() && mechanism == BY_COOKIE) {
|
|
return;
|
|
} else if (Blind) {
|
|
if (mechanism == BY_COOKIE)
|
|
pline(fortune_msg);
|
|
pline("What a pity that you cannot read it!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
line = getrumor(truth, buf, reading ? FALSE : TRUE);
|
|
if (!*line)
|
|
line = "NetHack rumors file closed for renovation.";
|
|
switch (mechanism) {
|
|
case BY_ORACLE:
|
|
/* Oracle delivers the rumor */
|
|
pline("True to her word, the Oracle %ssays: ",
|
|
(!rn2(4) ? "offhandedly "
|
|
: (!rn2(3) ? "casually "
|
|
: (rn2(2) ? "nonchalantly " : ""))));
|
|
SetVoice((struct monst *) 0, 0, 80, voice_oracle);
|
|
verbalize1(line);
|
|
/* [WIS exercised by getrumor()] */
|
|
return;
|
|
case BY_COOKIE:
|
|
pline(fortune_msg);
|
|
FALLTHROUGH;
|
|
/* FALLTHRU */
|
|
case BY_PAPER:
|
|
pline("It reads:");
|
|
break;
|
|
}
|
|
pline1(line);
|
|
}
|
|
|
|
staticfn void
|
|
init_oracles(dlb *fp)
|
|
{
|
|
int i;
|
|
char line[BUFSZ];
|
|
int cnt = 0;
|
|
|
|
/* this assumes we're only called once */
|
|
(void) dlb_fgets(line, sizeof line, fp); /* skip "don't edit" comment*/
|
|
(void) dlb_fgets(line, sizeof line, fp);
|
|
if (sscanf(line, "%5d\n", &cnt) == 1 && cnt > 0) {
|
|
svo.oracle_cnt = (unsigned) cnt;
|
|
go.oracle_loc = (unsigned long *) alloc((unsigned) cnt * sizeof(long));
|
|
for (i = 0; i < cnt; i++) {
|
|
(void) dlb_fgets(line, sizeof line, fp);
|
|
(void) sscanf(line, "%5lx\n", &go.oracle_loc[i]);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void
|
|
save_oracles(NHFILE *nhfp)
|
|
{
|
|
if (perform_bwrite(nhfp)) {
|
|
if (nhfp->structlevel)
|
|
bwrite(nhfp->fd, (genericptr_t) &svo.oracle_cnt,
|
|
sizeof svo.oracle_cnt);
|
|
if (svo.oracle_cnt) {
|
|
if (nhfp->structlevel) {
|
|
bwrite(nhfp->fd, (genericptr_t) go.oracle_loc,
|
|
svo.oracle_cnt * sizeof (long));
|
|
}
|
|
}
|
|
}
|
|
if (release_data(nhfp)) {
|
|
if (svo.oracle_cnt) {
|
|
free((genericptr_t) go.oracle_loc);
|
|
go.oracle_loc = 0, svo.oracle_cnt = 0, go.oracle_flg = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
restore_oracles(NHFILE *nhfp)
|
|
{
|
|
if (nhfp->structlevel)
|
|
mread(nhfp->fd, (genericptr_t) &svo.oracle_cnt, sizeof svo.oracle_cnt);
|
|
|
|
if (svo.oracle_cnt) {
|
|
go.oracle_loc = (unsigned long *) alloc(svo.oracle_cnt * sizeof(long));
|
|
if (nhfp->structlevel) {
|
|
mread(nhfp->fd, (genericptr_t) go.oracle_loc,
|
|
svo.oracle_cnt * sizeof (long));
|
|
}
|
|
go.oracle_flg = 1; /* no need to call init_oracles() */
|
|
}
|
|
}
|
|
|
|
void
|
|
outoracle(boolean special, boolean delphi)
|
|
{
|
|
winid tmpwin;
|
|
dlb *oracles;
|
|
int oracle_idx;
|
|
char *endp, line[COLNO], xbuf[BUFSZ];
|
|
|
|
/* early return if we couldn't open ORACLEFILE on previous attempt,
|
|
or if all the oracularities are already exhausted */
|
|
if (go.oracle_flg < 0 || (go.oracle_flg > 0 && svo.oracle_cnt == 0))
|
|
return;
|
|
|
|
oracles = dlb_fopen(ORACLEFILE, "r");
|
|
|
|
if (oracles) {
|
|
if (go.oracle_flg == 0) { /* if this is the first outoracle() */
|
|
init_oracles(oracles);
|
|
go.oracle_flg = 1;
|
|
if (svo.oracle_cnt == 0)
|
|
goto close_oracles;
|
|
}
|
|
/* oracle_loc[0] is the special oracle;
|
|
oracle_loc[1..oracle_cnt-1] are normal ones */
|
|
if (svo.oracle_cnt <= 1 && !special)
|
|
goto close_oracles; /*(shouldn't happen)*/
|
|
oracle_idx = special ? 0 : rnd((int) svo.oracle_cnt - 1);
|
|
(void) dlb_fseek(oracles, (long) go.oracle_loc[oracle_idx], SEEK_SET);
|
|
if (!special) /* move offset of very last one into this slot */
|
|
go.oracle_loc[oracle_idx] = go.oracle_loc[--svo.oracle_cnt];
|
|
|
|
tmpwin = create_nhwindow(NHW_TEXT);
|
|
if (delphi)
|
|
putstr(tmpwin, 0,
|
|
special
|
|
? "The Oracle scornfully takes all your gold and says:"
|
|
: "The Oracle meditates for a moment and then intones:");
|
|
else
|
|
putstr(tmpwin, 0, "The message reads:");
|
|
putstr(tmpwin, 0, "");
|
|
|
|
while (dlb_fgets(line, COLNO, oracles) && strcmp(line, "---\n")) {
|
|
if ((endp = strchr(line, '\n')) != 0)
|
|
*endp = 0;
|
|
putstr(tmpwin, 0, xcrypt(line, xbuf));
|
|
}
|
|
display_nhwindow(tmpwin, TRUE);
|
|
destroy_nhwindow(tmpwin);
|
|
close_oracles:
|
|
(void) dlb_fclose(oracles);
|
|
} else {
|
|
couldnt_open_file(ORACLEFILE);
|
|
go.oracle_flg = -1; /* don't try to open it again */
|
|
}
|
|
}
|
|
|
|
int
|
|
doconsult(struct monst *oracl)
|
|
{
|
|
long umoney;
|
|
int u_pay, minor_cost = 50, major_cost = 500 + 50 * u.ulevel;
|
|
int add_xpts;
|
|
char qbuf[QBUFSZ];
|
|
|
|
gm.multi = 0;
|
|
umoney = money_cnt(gi.invent);
|
|
|
|
if (!oracl) {
|
|
There("is no one here to consult.");
|
|
return ECMD_OK;
|
|
} else if (!oracl->mpeaceful) {
|
|
pline("%s is in no mood for consultations.", Monnam(oracl));
|
|
return ECMD_OK;
|
|
} else if (!umoney) {
|
|
You("have no gold.");
|
|
return ECMD_OK;
|
|
}
|
|
|
|
Sprintf(qbuf, "\"Wilt thou settle for a minor consultation?\" (%d %s)",
|
|
minor_cost, currency((long) minor_cost));
|
|
switch (ynq(qbuf)) {
|
|
default:
|
|
case 'q':
|
|
return ECMD_OK;
|
|
case 'y':
|
|
if (umoney < (long) minor_cost) {
|
|
You("don't even have enough gold for that!");
|
|
return ECMD_OK;
|
|
}
|
|
u_pay = minor_cost;
|
|
break;
|
|
case 'n':
|
|
if (umoney <= (long) minor_cost /* don't even ask */
|
|
|| (svo.oracle_cnt == 1 || go.oracle_flg < 0))
|
|
return ECMD_OK;
|
|
Sprintf(qbuf, "\"Then dost thou desire a major one?\" (%d %s)",
|
|
major_cost, currency((long) major_cost));
|
|
if (y_n(qbuf) != 'y')
|
|
return ECMD_OK;
|
|
u_pay = (umoney < (long) major_cost) ? (int) umoney : major_cost;
|
|
break;
|
|
}
|
|
money2mon(oracl, (long) u_pay);
|
|
disp.botl = TRUE;
|
|
if (!u.uevent.major_oracle && !u.uevent.minor_oracle)
|
|
record_achievement(ACH_ORCL);
|
|
add_xpts = 0; /* first oracle of each type gives experience points */
|
|
if (u_pay == minor_cost) {
|
|
outrumor(1, BY_ORACLE);
|
|
if (!u.uevent.minor_oracle)
|
|
add_xpts = u_pay / (u.uevent.major_oracle ? 25 : 10);
|
|
/* 5 pts if very 1st, or 2 pts if major already done */
|
|
u.uevent.minor_oracle = TRUE;
|
|
} else {
|
|
boolean cheapskate = u_pay < major_cost;
|
|
|
|
outoracle(cheapskate, TRUE);
|
|
if (!cheapskate && !u.uevent.major_oracle)
|
|
add_xpts = u_pay / (u.uevent.minor_oracle ? 25 : 10);
|
|
/* ~100 pts if very 1st, ~40 pts if minor already done */
|
|
u.uevent.major_oracle = TRUE;
|
|
exercise(A_WIS, !cheapskate);
|
|
}
|
|
if (add_xpts) {
|
|
more_experienced(add_xpts, u_pay / 50);
|
|
newexplevel();
|
|
}
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
staticfn void
|
|
couldnt_open_file(const char *filename)
|
|
{
|
|
int save_something = program_state.something_worth_saving;
|
|
|
|
/* most likely the file is missing, so suppress impossible()'s
|
|
"saving and restoring might fix this" (unless the fuzzer,
|
|
which escalates impossible to panic, is running) */
|
|
if (!iflags.debug_fuzzer)
|
|
program_state.something_worth_saving = 0;
|
|
|
|
impossible("Can't open '%s' file.", filename);
|
|
program_state.something_worth_saving = save_something;
|
|
}
|
|
|
|
/* is 'word' a capitalized monster name that should be preceded by "the"?
|
|
(non-unique monster like Mordor Orc, or capitalized title like Norn
|
|
rather than a name); used by the() on a string without any context;
|
|
this sets up a list of names rather than scan all of mons[] every time
|
|
the decision is needed (resulting list currently contains 27 monster
|
|
entries and 20 hallucination entries) */
|
|
boolean
|
|
CapitalMon(
|
|
const char *word) /* potential monster name; a name might be followed by
|
|
* something like " corpse" */
|
|
{
|
|
const char *nam;
|
|
unsigned i, wln, nln;
|
|
|
|
if (!word || !*word || *word == lowc(*word))
|
|
return FALSE; /* 'word' is not a capitalized monster name */
|
|
|
|
if (!CapMons)
|
|
init_CapMons();
|
|
assert(CapMons != 0);
|
|
|
|
wln = (unsigned) strlen(word);
|
|
for (i = 0; i < CapMonSiz - 1; ++i) {
|
|
nam = CapMons[i];
|
|
nln = (unsigned) strlen(nam);
|
|
if (wln < nln)
|
|
continue;
|
|
/*
|
|
* Unlike name_to_mon(), we don't need to find the longest match
|
|
* or return the gender or a pointer to trailing stuff. We do
|
|
* check full words though: "Foo" matches "Foo" and "Foo bar" and
|
|
* "Foo's bar" but not "Foobar". We use case-sensitive matching.
|
|
*/
|
|
if (!strncmp(nam, word, nln)
|
|
&& (!word[nln] || word[nln] == ' ' || word[nln] == '\''))
|
|
return TRUE; /* 'word' is a capitalized monster name */
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* one-time initialization of CapMons[], a list of non-unique monsters
|
|
having a capitalized type name like Green-elf or Archon, plus unique
|
|
monsters whose "name" is a title rather than a personal name, plus
|
|
hallucinatory monster names that fall into either of those categories */
|
|
staticfn void
|
|
init_CapMons(void)
|
|
{
|
|
unsigned pass;
|
|
dlb *bogonfile = dlb_fopen(BOGUSMONFILE, "r");
|
|
|
|
if (CapMons) /* sanity precaution */
|
|
free_CapMons();
|
|
|
|
/* first pass: count the number of relevant monster names, then
|
|
allocate memory for CapMons[]; second pass: populate CapMons[] */
|
|
for (pass = 1; pass <= 2; ++pass) {
|
|
struct permonst *mptr;
|
|
const char *nam;
|
|
unsigned mndx, mgend;
|
|
|
|
/* the first CapMonstCnt entries come from mons[].pmnames[] and
|
|
the next CapBogonCnt entries from the 'bogusmons' file;
|
|
there is an extra entry for Null at the end, but that is only
|
|
useful to force non-zero array size in case both mons[] and
|
|
bogusmons get modified to have no applicable monster names */
|
|
CapMonstCnt = CapBogonCnt = 0;
|
|
|
|
/* gather applicable actual monsters */
|
|
for (mndx = LOW_PM; mndx < NUMMONS; ++mndx) {
|
|
mptr = &mons[mndx];
|
|
if ((mptr->geno & G_UNIQ) != 0 && !the_unique_pm(mptr))
|
|
continue;
|
|
for (mgend = MALE; mgend < NUM_MGENDERS; ++mgend) {
|
|
nam = mptr->pmnames[mgend];
|
|
if (nam && *nam != lowc(*nam)) {
|
|
if (pass == 2)
|
|
CapMons[CapMonstCnt] = nam;
|
|
++CapMonstCnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* now gather applicable hallucinatory monsters */
|
|
if (bogonfile) {
|
|
char hline[BUFSZ], xbuf[BUFSZ], *endp, *startp, code;
|
|
|
|
/* rewind; effectively a no-op for pass 1; essential for pass 2 */
|
|
(void) dlb_fseek(bogonfile, 0L, SEEK_SET);
|
|
/* skip "don't edit" comment (first line of file) */
|
|
(void) dlb_fgets(hline, sizeof hline, bogonfile);
|
|
|
|
/* one monster name per line in rudimentary encrypted format;
|
|
some are prefixed by a classification code to indicate
|
|
gender and/or to distinguish an individual from a type
|
|
(code is a single punctuation character when present) */
|
|
while (dlb_fgets(hline, sizeof hline, bogonfile)) {
|
|
if ((endp = strchr(hline, '\n')) != 0)
|
|
*endp = '\0'; /* strip newline */
|
|
(void) xcrypt(hline, xbuf);
|
|
unpadline(xbuf);
|
|
|
|
if (!xbuf[0] || !strchr(bogon_codes, xbuf[0]))
|
|
code = '\0', startp = &xbuf[0]; /* ordinary */
|
|
else
|
|
code = xbuf[0], startp = &xbuf[1]; /* special */
|
|
|
|
if (*startp != lowc(*startp) && !bogon_is_pname(code)) {
|
|
if (pass == 2)
|
|
CapMons[CapMonstCnt + CapBogonCnt] = dupstr(startp);
|
|
++CapBogonCnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* finish the current pass */
|
|
if (pass == 1) {
|
|
CapMonSiz = CapMonstCnt + CapBogonCnt + 1; /* +1: terminator */
|
|
CapMons = (const char **) alloc(CapMonSiz * sizeof *CapMons);
|
|
} else { /* pass == 2 */
|
|
/* terminator; not strictly needed */
|
|
CapMons[CapMonSiz - 1] = (const char *) 0;
|
|
|
|
if (bogonfile)
|
|
(void) dlb_fclose(bogonfile), bogonfile = (dlb *) 0;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
/*
|
|
* CapMons[] init doesn't kick in until needed. To force this name
|
|
* dump, set DEBUGFILES to "CapMons" in your environment (or in
|
|
* sysconf) prior to starting nethack, wish for a statue of an Archon
|
|
* and drop it if held, then step away and apply a stethoscope towards
|
|
* it to trigger a message that passes "Archon" to the() which will
|
|
* then call CapitalMon() which in turn will call init_CapMons().
|
|
*/
|
|
if (wizard && explicitdebug("CapMons")) {
|
|
char buf[BUFSZ];
|
|
unsigned i;
|
|
winid tmpwin = create_nhwindow(NHW_TEXT);
|
|
|
|
putstr(tmpwin, 0,
|
|
"Capitalized monster type names normally preceded by \"the\":");
|
|
for (i = 0; i < CapMonSiz - 1; ++i) {
|
|
Sprintf(buf, " %.77s", CapMons[i]);
|
|
putstr(tmpwin, 0, buf);
|
|
}
|
|
display_nhwindow(tmpwin, TRUE);
|
|
destroy_nhwindow(tmpwin);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* release memory allocated for the list of capitalized monster type names */
|
|
void
|
|
free_CapMons(void)
|
|
{
|
|
/* note: some elements of CapMons[] are string literals from
|
|
mons[].pmnames[] and should not be freed, others are dynamically
|
|
allocated copies of hallucinatory monster names and should be freed */
|
|
if (CapMons) {
|
|
unsigned idx;
|
|
|
|
/* skip 0..MonstCnt-1, free MonstCnt..(MonstCnt+BogonCnt-1) */
|
|
for (idx = CapMonstCnt; idx < CapMonSiz - 1; ++idx)
|
|
free((genericptr_t) CapMons[idx]); /* cast: discard 'const' */
|
|
free((genericptr_t) CapMons), CapMons = (const char **) 0;
|
|
}
|
|
CapMonSiz = 0;
|
|
}
|
|
|
|
/*rumors.c*/
|