]> granicus.if.org Git - nethack/commitdiff
fix pull request #636 - the("Capitalized Monster")
authorPatR <rankin@nethack.org>
Wed, 24 Nov 2021 08:24:56 +0000 (00:24 -0800)
committerPatR <rankin@nethack.org>
Wed, 24 Nov 2021 08:24:56 +0000 (00:24 -0800)
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

doc/fixes37.0
include/extern.h
src/objnam.c
src/rumors.c
src/save.c

index 4ee60073892f6a021d0f9492c6ac97506b67d190..3716cdccb93333619af0b70ebce3f12caa7f469f 100644 (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
index b42c421122917f1f767ba9efbb14cea584ad1d64..f112176ce732c847e6639021ef9ce4f8539aa708 100644 (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 ### */
 
index a59048dec64872a49506aa8b2503424df7ef4a8b..759c6fd82430cf6ce75951d4ef1d8128f35fdece 100644 (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)) {
index 3adfc4432a52fe8def833633d3b6450ef146d88d..39985734bab5ac9317febaaa03b58a81529fa6bc 100644 (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*/
index 250210de80dcdc3be75ab8e3b24a5dabda3d79ba..364423e90f7dda40eb9302fe8b72887c85a89948 100644 (file)
@@ -1080,6 +1080,7 @@ freedynamicdata(void)
     freenames();
     free_waterlevel();
     free_dungeons();
+    free_CapMons();
 
     /* some pointers in iflags */
     if (iflags.wc_font_map)