From: PatR Date: Wed, 24 Nov 2021 08:24:56 +0000 (-0800) Subject: fix pull request #636 - the("Capitalized Monster") X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b2d4b77d3a1858869773e5c5bc2384a953b38e6a;p=nethack 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 --- diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 4ee600738..3716cdccb 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -686,6 +686,8 @@ if #untrap monst-from-web failure happened while hero was standing on a spot the expected " 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 diff --git a/include/extern.h b/include/extern.h index b42c42112..f112176ce 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 ### */ diff --git a/src/objnam.c b/src/objnam.c index a59048dec..759c6fd82 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -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)) { diff --git a/src/rumors.c b/src/rumors.c index 3adfc4432..39985734b 100644 --- a/src/rumors.c +++ b/src/rumors.c @@ -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*/ diff --git a/src/save.c b/src/save.c index 250210de8..364423e90 100644 --- a/src/save.c +++ b/src/save.c @@ -1080,6 +1080,7 @@ freedynamicdata(void) freenames(); free_waterlevel(); free_dungeons(); + free_CapMons(); /* some pointers in iflags */ if (iflags.wc_font_map)