fix pull request #636 - the("Capitalized Monster")

Function the() wasn't supposed to be used for monsters because many
of the ones with capitalized names confuse it, but over time multiple
instances of the(mon_nam()) have crept into the code.  Instead of
ripping those out, modify the() to handle that situation better.

Pull request #636 by entrez dealt with this with one extra line of
code, but could end up scanning all the names in mons[] repeatedly
if the("Capitalized string") gets called a lot.  This uses a similar
one line fix but calls a whole new routine that scans through mons[]
once collecting all the relevant special case names.  As a bonus,
it does the same for hallucinatory monster names which name_to_mon()
couldn't handle.

Fixes #626
This commit is contained in:
PatR
2021-11-24 00:24:56 -08:00
parent dc4b98ebdc
commit b2d4b77d3a
5 changed files with 195 additions and 1 deletions

View File

@@ -686,6 +686,8 @@ if #untrap monst-from-web failure happened while hero was standing on a spot
the expected "<monst> remains entangled" feedback wasn't delivered
if hero is wearing an amulet of magical breathing and polymorphs into a fish
or sea monster, don't lose health for turns spent out of water
fix up some "the" handling for monsters whose type name is upper case to avoid
"Uruk-hai is healthy for a statue", "You can't polymorph into Oracle"
Fixes to 3.7.0-x Problems that Were Exposed Via git Repository

View File

@@ -2270,6 +2270,8 @@ extern void save_oracles(NHFILE *);
extern void restore_oracles(NHFILE *);
extern int doconsult(struct monst *);
extern void rumor_check(void);
extern boolean CapitalMon(const char *);
extern void free_CapMons(void);
/* ### save.c ### */

View File

@@ -1524,6 +1524,9 @@ corpse_xname(
to precede capitalized unique monsters (pnames are handled above) */
if (the_prefix)
Strcat(nambuf, "the ");
/* note: over time, various instances of the(mon_name()) have crept
into the code, so the() has been modified to deal with capitalized
monster names; we could switch to using it below like an() */
if (!adjective || !*adjective) {
/* normal case: newt corpse */
@@ -1822,6 +1825,8 @@ the(const char* str)
Strcpy(&buf[1], str + 1);
return buf;
} else if (*str < 'A' || *str > 'Z'
/* some capitalized monster names want "the", others don't */
|| CapitalMon(str)
/* treat named fruit as not a proper name, even if player
has assigned a capitalized proper name as his/her fruit */
|| fruit_from_name(str, TRUE, (int *) 0)) {

View File

@@ -44,6 +44,12 @@ static void init_rumors(dlb *);
static void init_oracles(dlb *);
static void others_check(const char *ftype, const char *, winid *);
static void couldnt_open_file(const char *);
static 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 CapMonSiz = 0;
static const char **CapMons = 0;
DISABLE_WARNING_FORMAT_NONLITERAL
@@ -404,12 +410,15 @@ get_rnd_text(const char* fname, char* buf, int (*rng)(int))
endtxt = dlb_ftell(fh);
sizetxt = endtxt - starttxt;
/* might be zero (only if file is empty); should complain in that
case but if could happen over and over, also the suggestion
case but it could happen over and over, also the suggestion
that save and restore might fix the problem wouldn't be useful */
if (sizetxt < 1L)
return buf;
tidbit = (*rng)(sizetxt);
/* position randomly which will probably be in the middle of a line;
read the rest of that line, then use the next one; if there's no
next one (ie, end of file), go back to beginning and use first */
(void) dlb_fseek(fh, starttxt + tidbit, SEEK_SET);
(void) dlb_fgets(line, sizeof line, fh);
if (!dlb_fgets(line, sizeof line, fh)) {
@@ -676,4 +685,179 @@ couldnt_open_file(const char *filename)
g.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();
wln = (unsigned) strlen(word);
for (i = 0; i < CapMonSiz - 1; ++i) {
nam = CapMons[i];
if (*nam == '\033') /* if dynamic alloc flag is present, skip it */
++nam;
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" but
* not "Foobar". We use case-sensitive matching here.
*/
if (!strncmp(nam, 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 */
static 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, count;
count = 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[count] = nam;
++count;
}
}
}
/* now gather applicable hallucinatory monsters; don't reset count */
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 = index(hline, '\n')) != 0)
*endp = '\0'; /* strip newline */
(void) xcrypt(hline, xbuf);
if (letter(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) {
/* insert a "dynamically allocated flag" and save a
copy of bogus monst name without its prefix code */
hline[0] = '\033';
Strcpy(&hline[1], startp);
CapMons[count] = dupstr(hline);
}
++count;
}
}
}
/* finish the current pass */
if (pass == 1) {
CapMonSiz = count + 1; /* +1: room for terminator */
CapMons = (const char **) alloc(CapMonSiz * sizeof *CapMons);
} else { /* pass == 2 */
/* terminator; not strictly needed */
CapMons[count] = (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 stethscope 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];
const char *nam;
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) {
nam = CapMons[i];
if (*nam == '\033')
++nam;
Sprintf(buf, " %.77s", nam);
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;
the first character for each element of the latter group is ESC */
if (CapMons) {
unsigned idx;
for (idx = 0; idx < CapMonSiz - 1; ++idx)
if (CapMons[idx] && *CapMons[idx] == '\033')
free((genericptr_t) CapMons[idx]); /* cast: discard 'const' */
free((genericptr_t) CapMons), CapMons = (const char **) 0;
}
CapMonSiz = 0;
}
/*rumors.c*/

View File

@@ -1080,6 +1080,7 @@ freedynamicdata(void)
freenames();
free_waterlevel();
free_dungeons();
free_CapMons();
/* some pointers in iflags */
if (iflags.wc_font_map)