]> granicus.if.org Git - nethack/commitdiff
another dictionary consulted, another couple of prefixes
authornhmall <nhmall@nethack.org>
Sun, 18 Mar 2018 13:54:18 +0000 (09:54 -0400)
committernhmall <nhmall@nethack.org>
Sun, 18 Mar 2018 13:54:18 +0000 (09:54 -0400)
objnam.c [new file with mode: 0644]

diff --git a/objnam.c b/objnam.c
new file mode 100644 (file)
index 0000000..ecb5de3
--- /dev/null
+++ b/objnam.c
@@ -0,0 +1,4062 @@
+/* NetHack 3.6 objnam.c        $NHDT-Date: 1521377345 2018/03/18 12:49:05 $  $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.194 $ */
+/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
+/* NetHack may be freely redistributed.  See license for details. */
+
+#include "hack.h"
+
+/* "an uncursed greased partly eaten guardian naga hatchling [corpse]" */
+#define PREFIX 80 /* (56) */
+#define SCHAR_LIM 127
+#define NUMOBUF 12
+
+STATIC_DCL char *FDECL(strprepend, (char *, const char *));
+STATIC_DCL short FDECL(rnd_otyp_by_wpnskill, (SCHAR_P));
+STATIC_DCL short FDECL(rnd_otyp_by_namedesc, (char *, CHAR_P));
+STATIC_DCL boolean FDECL(wishymatch, (const char *, const char *, BOOLEAN_P));
+STATIC_DCL char *NDECL(nextobuf);
+STATIC_DCL void FDECL(releaseobuf, (char *));
+STATIC_DCL char *FDECL(minimal_xname, (struct obj *));
+STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *));
+STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned));
+STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P,
+                                           const char *const *));
+STATIC_DCL char *FDECL(singplur_compound, (char *));
+STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned));
+STATIC_DCL boolean FDECL(badman, (const char *, BOOLEAN_P));
+
+struct Jitem {
+    int item;
+    const char *name;
+};
+
+#define BSTRCMPI(base, ptr, str) ((ptr) < base || strcmpi((ptr), str))
+#define BSTRNCMPI(base, ptr, str, num) \
+    ((ptr) < base || strncmpi((ptr), str, num))
+#define Strcasecpy(dst, src) (void) strcasecpy(dst, src)
+
+/* true for gems/rocks that should have " stone" appended to their names */
+#define GemStone(typ)                                                  \
+    (typ == FLINT                                                      \
+     || (objects[typ].oc_material == GEMSTONE                          \
+         && (typ != DILITHIUM_CRYSTAL && typ != RUBY && typ != DIAMOND \
+             && typ != SAPPHIRE && typ != BLACK_OPAL && typ != EMERALD \
+             && typ != OPAL)))
+
+STATIC_OVL struct Jitem Japanese_items[] = { { SHORT_SWORD, "wakizashi" },
+                                             { BROADSWORD, "ninja-to" },
+                                             { FLAIL, "nunchaku" },
+                                             { GLAIVE, "naginata" },
+                                             { LOCK_PICK, "osaku" },
+                                             { WOODEN_HARP, "koto" },
+                                             { KNIFE, "shito" },
+                                             { PLATE_MAIL, "tanko" },
+                                             { HELMET, "kabuto" },
+                                             { LEATHER_GLOVES, "yugake" },
+                                             { FOOD_RATION, "gunyoki" },
+                                             { POT_BOOZE, "sake" },
+                                             { 0, "" } };
+
+STATIC_DCL const char *FDECL(Japanese_item_name, (int i));
+
+STATIC_OVL char *
+strprepend(s, pref)
+register char *s;
+register const char *pref;
+{
+    register int i = (int) strlen(pref);
+
+    if (i > PREFIX) {
+        impossible("PREFIX too short (for %d).", i);
+        return s;
+    }
+    s -= i;
+    (void) strncpy(s, pref, i); /* do not copy trailing 0 */
+    return s;
+}
+
+/* manage a pool of BUFSZ buffers, so callers don't have to */
+static char NEARDATA obufs[NUMOBUF][BUFSZ];
+static int obufidx = 0;
+
+STATIC_OVL char *
+nextobuf()
+{
+    obufidx = (obufidx + 1) % NUMOBUF;
+    return obufs[obufidx];
+}
+
+/* put the most recently allocated buffer back if possible */
+STATIC_OVL void
+releaseobuf(bufp)
+char *bufp;
+{
+    /* caller may not know whether bufp is the most recently allocated
+       buffer; if it isn't, do nothing; note that because of the somewhat
+       obscure PREFIX handling for object name formatting by xname(),
+       the pointer our caller has and is passing to us might be into the
+       middle of an obuf rather than the address returned by nextobuf() */
+    if (bufp >= obufs[obufidx]
+        && bufp < obufs[obufidx] + sizeof obufs[obufidx]) /* obufs[][BUFSZ] */
+        obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF;
+}
+
+char *
+obj_typename(otyp)
+register int otyp;
+{
+    char *buf = nextobuf();
+    struct objclass *ocl = &objects[otyp];
+    const char *actualn = OBJ_NAME(*ocl);
+    const char *dn = OBJ_DESCR(*ocl);
+    const char *un = ocl->oc_uname;
+    int nn = ocl->oc_name_known;
+
+    if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp))
+        actualn = Japanese_item_name(otyp);
+    switch (ocl->oc_class) {
+    case COIN_CLASS:
+        Strcpy(buf, "coin");
+        break;
+    case POTION_CLASS:
+        Strcpy(buf, "potion");
+        break;
+    case SCROLL_CLASS:
+        Strcpy(buf, "scroll");
+        break;
+    case WAND_CLASS:
+        Strcpy(buf, "wand");
+        break;
+    case SPBOOK_CLASS:
+        if (otyp != SPE_NOVEL) {
+            Strcpy(buf, "spellbook");
+        } else {
+            Strcpy(buf, !nn ? "book" : "novel");
+            nn = 0;
+        }
+        break;
+    case RING_CLASS:
+        Strcpy(buf, "ring");
+        break;
+    case AMULET_CLASS:
+        if (nn)
+            Strcpy(buf, actualn);
+        else
+            Strcpy(buf, "amulet");
+        if (un)
+            Sprintf(eos(buf), " called %s", un);
+        if (dn)
+            Sprintf(eos(buf), " (%s)", dn);
+        return buf;
+    default:
+        if (nn) {
+            Strcpy(buf, actualn);
+            if (GemStone(otyp))
+                Strcat(buf, " stone");
+            if (un)
+                Sprintf(eos(buf), " called %s", un);
+            if (dn)
+                Sprintf(eos(buf), " (%s)", dn);
+        } else {
+            Strcpy(buf, dn ? dn : actualn);
+            if (ocl->oc_class == GEM_CLASS)
+                Strcat(buf,
+                       (ocl->oc_material == MINERAL) ? " stone" : " gem");
+            if (un)
+                Sprintf(eos(buf), " called %s", un);
+        }
+        return buf;
+    }
+    /* here for ring/scroll/potion/wand */
+    if (nn) {
+        if (ocl->oc_unique)
+            Strcpy(buf, actualn); /* avoid spellbook of Book of the Dead */
+        else
+            Sprintf(eos(buf), " of %s", actualn);
+    }
+    if (un)
+        Sprintf(eos(buf), " called %s", un);
+    if (dn)
+        Sprintf(eos(buf), " (%s)", dn);
+    return buf;
+}
+
+/* less verbose result than obj_typename(); either the actual name
+   or the description (but not both); user-assigned name is ignored */
+char *
+simple_typename(otyp)
+int otyp;
+{
+    char *bufp, *pp, *save_uname = objects[otyp].oc_uname;
+
+    objects[otyp].oc_uname = 0; /* suppress any name given by user */
+    bufp = obj_typename(otyp);
+    objects[otyp].oc_uname = save_uname;
+    if ((pp = strstri(bufp, " (")) != 0)
+        *pp = '\0'; /* strip the appended description */
+    return bufp;
+}
+
+boolean
+obj_is_pname(obj)
+struct obj *obj;
+{
+    if (!obj->oartifact || !has_oname(obj))
+        return FALSE;
+    if (!program_state.gameover && !iflags.override_ID) {
+        if (not_fully_identified(obj))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+/* used by distant_name() to pass extra information to xname_flags();
+   it would be much cleaner if this were a parameter, but that would
+   require all of the xname() and doname() calls to be modified */
+static int distantname = 0;
+
+/* Give the name of an object seen at a distance.  Unlike xname/doname,
+ * we don't want to set dknown if it's not set already.
+ */
+char *
+distant_name(obj, func)
+struct obj *obj;
+char *FDECL((*func), (OBJ_P));
+{
+    char *str;
+
+    /* 3.6.1: this used to save Blind, set it, make the call, then restore
+     * the saved value; but the Eyes of the Overworld override blindness
+     * and let characters wearing them get dknown set for distant items.
+     *
+     * TODO? if the hero is wearing those Eyes, figure out whether the
+     * object is within X-ray radius and only treat it as distant when
+     * beyond that radius.  Logic is iffy but result might be interesting.
+     */
+    ++distantname;
+    str = (*func)(obj);
+    --distantname;
+    return str;
+}
+
+/* convert player specified fruit name into corresponding fruit juice name
+   ("slice of pizza" -> "pizza juice" rather than "slice of pizza juice") */
+char *
+fruitname(juice)
+boolean juice; /* whether or not to append " juice" to the name */
+{
+    char *buf = nextobuf();
+    const char *fruit_nam = strstri(pl_fruit, " of ");
+
+    if (fruit_nam)
+        fruit_nam += 4; /* skip past " of " */
+    else
+        fruit_nam = pl_fruit; /* use it as is */
+
+    Sprintf(buf, "%s%s", makesingular(fruit_nam), juice ? " juice" : "");
+    return buf;
+}
+
+/* look up a named fruit by index (1..127) */
+struct fruit *
+fruit_from_indx(indx)
+int indx;
+{
+    struct fruit *f;
+
+    for (f = ffruit; f; f = f->nextf)
+        if (f->fid == indx)
+            break;
+    return f;
+}
+
+/* look up a named fruit by name */
+struct fruit *
+fruit_from_name(fname, exact, highest_fid)
+const char *fname;
+boolean exact; /* False => prefix or exact match, True = exact match only */
+int *highest_fid; /* optional output; only valid if 'fname' isn't found */
+{
+    struct fruit *f, *tentativef;
+    char *altfname;
+    unsigned k;
+    /*
+     * note: named fruits are case-senstive...
+     */
+
+    if (highest_fid)
+        *highest_fid = 0;
+    /* first try for an exact match */
+    for (f = ffruit; f; f = f->nextf)
+        if (!strcmp(f->fname, fname))
+            return f;
+        else if (highest_fid && f->fid > *highest_fid)
+            *highest_fid = f->fid;
+
+    /* didn't match as-is; if caller is willing to accept a prefix
+       match, try to find one; we want to find the longest prefix that
+       matches, not the first */
+    if (!exact) {
+        tentativef = 0;
+        for (f = ffruit; f; f = f->nextf) {
+            k = strlen(f->fname);
+            if (!strncmp(f->fname, fname, k)
+                && (!fname[k] || fname[k] == ' ')
+                && (!tentativef || k > strlen(tentativef->fname)))
+                tentativef = f;
+        }
+        f = tentativef;
+    }
+    /* if we still don't have a match, try singularizing the target;
+       for exact match, that's trivial, but for prefix, it's hard */
+    if (!f) {
+        altfname = makesingular(fname);
+        for (f = ffruit; f; f = f->nextf) {
+            if (!strcmp(f->fname, altfname))
+                break;
+        }
+        releaseobuf(altfname);
+    }
+    if (!f && !exact) {
+        char fnamebuf[BUFSZ], *p;
+        unsigned fname_k = strlen(fname); /* length of assumed plural fname */
+
+        tentativef = 0;
+        for (f = ffruit; f; f = f->nextf) {
+            k = strlen(f->fname);
+            /* reload fnamebuf[] each iteration in case it gets modified;
+               there's no need to recalculate fname_k */
+            Strcpy(fnamebuf, fname);
+            /* bug? if singular of fname is longer than plural,
+               failing the 'fname_k > k' test could skip a viable
+               candidate; unfortunately, we can't singularize until
+               after stripping off trailing stuff and we can't get
+               accurate fname_k until fname has been singularized;
+               compromise and use 'fname_k >= k' instead of '>',
+               accepting 1 char length discrepancy without risking
+               false match (I hope...) */
+            if (fname_k >= k && (p = index(&fnamebuf[k], ' ')) != 0) {
+                *p = '\0'; /* truncate at 1st space past length of f->fname */
+                altfname = makesingular(fnamebuf);
+                k = strlen(altfname); /* actually revised 'fname_k' */
+                if (!strcmp(f->fname, altfname)
+                    && (!tentativef || k > strlen(tentativef->fname)))
+                    tentativef = f;
+                releaseobuf(altfname); /* avoid churning through all obufs */
+            }
+        }
+        f = tentativef;
+    }
+    return f;
+}
+
+/* sort the named-fruit linked list by fruit index number */
+void
+reorder_fruit(forward)
+boolean forward;
+{
+    struct fruit *f, *allfr[1 + 127];
+    int i, j, k = SIZE(allfr);
+
+    for (i = 0; i < k; ++i)
+        allfr[i] = (struct fruit *) 0;
+    for (f = ffruit; f; f = f->nextf) {
+        /* without sanity checking, this would reduce to 'allfr[f->fid]=f' */
+        j = f->fid;
+        if (j < 1 || j >= k) {
+            impossible("reorder_fruit: fruit index (%d) out of range", j);
+            return; /* don't sort after all; should never happen... */
+        } else if (allfr[j]) {
+            impossible("reorder_fruit: duplicate fruit index (%d)", j);
+            return;
+        }
+        allfr[j] = f;
+    }
+    ffruit = 0; /* reset linked list; we're rebuilding it from scratch */
+    /* slot [0] will always be empty; must start 'i' at 1 to avoid
+       [k - i] being out of bounds during first iteration */
+    for (i = 1; i < k; ++i) {
+        /* for forward ordering, go through indices from high to low;
+           for backward ordering, go from low to high */
+        j = forward ? (k - i) : i;
+        if (allfr[j]) {
+            allfr[j]->nextf = ffruit;
+            ffruit = allfr[j];
+        }
+    }
+}
+
+char *
+xname(obj)
+struct obj *obj;
+{
+    return xname_flags(obj, CXN_NORMAL);
+}
+
+char *
+xname_flags(obj, cxn_flags)
+register struct obj *obj;
+unsigned cxn_flags; /* bitmask of CXN_xxx values */
+{
+    register char *buf;
+    register int typ = obj->otyp;
+    register struct objclass *ocl = &objects[typ];
+    int nn = ocl->oc_name_known, omndx = obj->corpsenm;
+    const char *actualn = OBJ_NAME(*ocl);
+    const char *dn = OBJ_DESCR(*ocl) ? OBJ_DESCR(*ocl) : actualn;
+    const char *un = ocl->oc_uname;
+    boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR);
+    boolean known, dknown, bknown;
+
+    buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */
+    if (Role_if(PM_SAMURAI) && Japanese_item_name(typ))
+        actualn = Japanese_item_name(typ);
+
+    buf[0] = '\0';
+    /*
+     * clean up known when it's tied to oc_name_known, eg after AD_DRIN
+     * This is only required for unique objects since the article
+     * printed for the object is tied to the combination of the two
+     * and printing the wrong article gives away information.
+     */
+    if (!nn && ocl->oc_uses_known && ocl->oc_unique)
+        obj->known = 0;
+    if (!Blind && !distantname)
+        obj->dknown = TRUE;
+    if (Role_if(PM_PRIEST))
+        obj->bknown = TRUE;
+
+    if (iflags.override_ID) {
+        known = dknown = bknown = TRUE;
+        nn = 1;
+    } else {
+        known = obj->known;
+        dknown = obj->dknown;
+        bknown = obj->bknown;
+    }
+
+    if (obj_is_pname(obj))
+        goto nameit;
+    switch (obj->oclass) {
+    case AMULET_CLASS:
+        if (!dknown)
+            Strcpy(buf, "amulet");
+        else if (typ == AMULET_OF_YENDOR || typ == FAKE_AMULET_OF_YENDOR)
+            /* each must be identified individually */
+            Strcpy(buf, known ? actualn : dn);
+        else if (nn)
+            Strcpy(buf, actualn);
+        else if (un)
+            Sprintf(buf, "amulet called %s", un);
+        else
+            Sprintf(buf, "%s amulet", dn);
+        break;
+    case WEAPON_CLASS:
+        if (is_poisonable(obj) && obj->opoisoned)
+            Strcpy(buf, "poisoned ");
+    case VENOM_CLASS:
+    case TOOL_CLASS:
+        if (typ == LENSES)
+            Strcpy(buf, "pair of ");
+        else if (is_wet_towel(obj))
+            Strcpy(buf, (obj->spe < 3) ? "moist " : "wet ");
+
+        if (!dknown)
+            Strcat(buf, dn);
+        else if (nn)
+            Strcat(buf, actualn);
+        else if (un) {
+            Strcat(buf, dn);
+            Strcat(buf, " called ");
+            Strcat(buf, un);
+        } else
+            Strcat(buf, dn);
+        /* If we use an() here we'd have to remember never to use */
+        /* it whenever calling doname() or xname(). */
+        if (typ == FIGURINE && omndx != NON_PM) {
+            Sprintf(eos(buf), " of a%s %s",
+                    index(vowels, *mons[omndx].mname) ? "n" : "",
+                    mons[omndx].mname);
+        } else if (is_wet_towel(obj)) {
+            if (wizard)
+                Sprintf(eos(buf), " (%d)", obj->spe);
+        }
+        break;
+    case ARMOR_CLASS:
+        /* depends on order of the dragon scales objects */
+        if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) {
+            Sprintf(buf, "set of %s", actualn);
+            break;
+        }
+        if (is_boots(obj) || is_gloves(obj))
+            Strcpy(buf, "pair of ");
+
+        if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD
+            && !dknown) {
+            Strcpy(buf, "shield");
+            break;
+        }
+        if (obj->otyp == SHIELD_OF_REFLECTION && !dknown) {
+            Strcpy(buf, "smooth shield");
+            break;
+        }
+
+        if (nn)
+            Strcat(buf, actualn);
+        else if (un) {
+            if (is_boots(obj))
+                Strcat(buf, "boots");
+            else if (is_gloves(obj))
+                Strcat(buf, "gloves");
+            else if (is_cloak(obj))
+                Strcpy(buf, "cloak");
+            else if (is_helmet(obj))
+                Strcpy(buf, "helmet");
+            else if (is_shield(obj))
+                Strcpy(buf, "shield");
+            else
+                Strcpy(buf, "armor");
+            Strcat(buf, " called ");
+            Strcat(buf, un);
+        } else
+            Strcat(buf, dn);
+        break;
+    case FOOD_CLASS:
+        if (typ == SLIME_MOLD) {
+            struct fruit *f = fruit_from_indx(obj->spe);
+
+            if (!f) {
+                impossible("Bad fruit #%d?", obj->spe);
+                Strcpy(buf, "fruit");
+            } else {
+                Strcpy(buf, f->fname);
+                if (pluralize) {
+                    /* ick; already pluralized fruit names
+                       are allowed--we want to try to avoid
+                       adding a redundant plural suffix */
+                    Strcpy(buf, makeplural(makesingular(buf)));
+                    pluralize = FALSE;
+                }
+            }
+            break;
+        }
+        if (obj->globby) {
+            Sprintf(buf, "%s%s",
+                    (obj->owt <= 100)
+                       ? "small "
+                       : (obj->owt > 500)
+                          ? "very large "
+                          : (obj->owt > 300)
+                             ? "large "
+                             : "",
+                    actualn);
+            break;
+        }
+
+        Strcpy(buf, actualn);
+        if (typ == TIN && known)
+            tin_details(obj, omndx, buf);
+        break;
+    case COIN_CLASS:
+    case CHAIN_CLASS:
+        Strcpy(buf, actualn);
+        break;
+    case ROCK_CLASS:
+        if (typ == STATUE && omndx != NON_PM)
+            Sprintf(buf, "%s%s of %s%s",
+                    (Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC))
+                       ? "historic "
+                       : "",
+                    actualn,
+                    type_is_pname(&mons[omndx])
+                       ? ""
+                       : the_unique_pm(&mons[omndx])
+                          ? "the "
+                          : index(vowels, *mons[omndx].mname)
+                             ? "an "
+                             : "a ",
+                    mons[omndx].mname);
+        else
+            Strcpy(buf, actualn);
+        break;
+    case BALL_CLASS:
+        Sprintf(buf, "%sheavy iron ball",
+                (obj->owt > ocl->oc_weight) ? "very " : "");
+        break;
+    case POTION_CLASS:
+        if (dknown && obj->odiluted)
+            Strcpy(buf, "diluted ");
+        if (nn || un || !dknown) {
+            Strcat(buf, "potion");
+            if (!dknown)
+                break;
+            if (nn) {
+                Strcat(buf, " of ");
+                if (typ == POT_WATER && bknown
+                    && (obj->blessed || obj->cursed)) {
+                    Strcat(buf, obj->blessed ? "holy " : "unholy ");
+                }
+                Strcat(buf, actualn);
+            } else {
+                Strcat(buf, " called ");
+                Strcat(buf, un);
+            }
+        } else {
+            Strcat(buf, dn);
+            Strcat(buf, " potion");
+        }
+        break;
+    case SCROLL_CLASS:
+        Strcpy(buf, "scroll");
+        if (!dknown)
+            break;
+        if (nn) {
+            Strcat(buf, " of ");
+            Strcat(buf, actualn);
+        } else if (un) {
+            Strcat(buf, " called ");
+            Strcat(buf, un);
+        } else if (ocl->oc_magic) {
+            Strcat(buf, " labeled ");
+            Strcat(buf, dn);
+        } else {
+            Strcpy(buf, dn);
+            Strcat(buf, " scroll");
+        }
+        break;
+    case WAND_CLASS:
+        if (!dknown)
+            Strcpy(buf, "wand");
+        else if (nn)
+            Sprintf(buf, "wand of %s", actualn);
+        else if (un)
+            Sprintf(buf, "wand called %s", un);
+        else
+            Sprintf(buf, "%s wand", dn);
+        break;
+    case SPBOOK_CLASS:
+        if (typ == SPE_NOVEL) { /* 3.6 tribute */
+            if (!dknown)
+                Strcpy(buf, "book");
+            else if (nn)
+                Strcpy(buf, actualn);
+            else if (un)
+                Sprintf(buf, "novel called %s", un);
+            else
+                Sprintf(buf, "%s book", dn);
+            break;
+            /* end of tribute */
+        } else if (!dknown) {
+            Strcpy(buf, "spellbook");
+        } else if (nn) {
+            if (typ != SPE_BOOK_OF_THE_DEAD)
+                Strcpy(buf, "spellbook of ");
+            Strcat(buf, actualn);
+        } else if (un) {
+            Sprintf(buf, "spellbook called %s", un);
+        } else
+            Sprintf(buf, "%s spellbook", dn);
+        break;
+    case RING_CLASS:
+        if (!dknown)
+            Strcpy(buf, "ring");
+        else if (nn)
+            Sprintf(buf, "ring of %s", actualn);
+        else if (un)
+            Sprintf(buf, "ring called %s", un);
+        else
+            Sprintf(buf, "%s ring", dn);
+        break;
+    case GEM_CLASS: {
+        const char *rock = (ocl->oc_material == MINERAL) ? "stone" : "gem";
+
+        if (!dknown) {
+            Strcpy(buf, rock);
+        } else if (!nn) {
+            if (un)
+                Sprintf(buf, "%s called %s", rock, un);
+            else
+                Sprintf(buf, "%s %s", dn, rock);
+        } else {
+            Strcpy(buf, actualn);
+            if (GemStone(typ))
+                Strcat(buf, " stone");
+        }
+        break;
+    }
+    default:
+        Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe);
+    }
+    if (pluralize)
+        Strcpy(buf, makeplural(buf));
+
+    if (obj->otyp == T_SHIRT && program_state.gameover) {
+        char tmpbuf[BUFSZ];
+
+        Sprintf(eos(buf), " with text \"%s\"", tshirt_text(obj, tmpbuf));
+    }
+
+    if (has_oname(obj) && dknown) {
+        Strcat(buf, " named ");
+    nameit:
+        Strcat(buf, ONAME(obj));
+    }
+
+    if (!strncmpi(buf, "the ", 4))
+        buf += 4;
+    return buf;
+}
+
+/* similar to simple_typename but minimal_xname operates on a particular
+   object rather than its general type; it formats the most basic info:
+     potion                     -- if description not known
+     brown potion               -- if oc_name_known not set
+     potion of object detection -- if discovered
+ */
+STATIC_OVL char *
+minimal_xname(obj)
+struct obj *obj;
+{
+    char *bufp;
+    struct obj bareobj;
+    struct objclass saveobcls;
+    int otyp = obj->otyp;
+
+    /* suppress user-supplied name */
+    saveobcls.oc_uname = objects[otyp].oc_uname;
+    objects[otyp].oc_uname = 0;
+    /* suppress actual name if object's description is unknown */
+    saveobcls.oc_name_known = objects[otyp].oc_name_known;
+    if (!obj->dknown)
+        objects[otyp].oc_name_known = 0;
+
+    /* caveat: this makes a lot of assumptions about which fields
+       are required in order for xname() to yield a sensible result */
+    bareobj = zeroobj;
+    bareobj.otyp = otyp;
+    bareobj.oclass = obj->oclass;
+    bareobj.dknown = obj->dknown;
+    /* suppress known except for amulets (needed for fakes and real A-of-Y) */
+    bareobj.known = (obj->oclass == AMULET_CLASS)
+                        ? obj->known
+                        /* default is "on" for types which don't use it */
+                        : !objects[otyp].oc_uses_known;
+    bareobj.quan = 1L;         /* don't want plural */
+    bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */
+    /* but suppressing fruit details leads to "bad fruit #0"
+       [perhaps we should force "slime mold" rather than use xname?] */
+    if (obj->otyp == SLIME_MOLD)
+        bareobj.spe = obj->spe;
+
+    bufp = distant_name(&bareobj, xname); /* xname(&bareobj) */
+    if (!strncmp(bufp, "uncursed ", 9))
+        bufp += 9; /* Role_if(PM_PRIEST) */
+
+    objects[otyp].oc_uname = saveobcls.oc_uname;
+    objects[otyp].oc_name_known = saveobcls.oc_name_known;
+    return bufp;
+}
+
+/* xname() output augmented for multishot missile feedback */
+char *
+mshot_xname(obj)
+struct obj *obj;
+{
+    char tmpbuf[BUFSZ];
+    char *onm = xname(obj);
+
+    if (m_shot.n > 1 && m_shot.o == obj->otyp) {
+        /* "the Nth arrow"; value will eventually be passed to an() or
+           The(), both of which correctly handle this "the " prefix */
+        Sprintf(tmpbuf, "the %d%s ", m_shot.i, ordin(m_shot.i));
+        onm = strprepend(onm, tmpbuf);
+    }
+    return onm;
+}
+
+/* used for naming "the unique_item" instead of "a unique_item" */
+boolean
+the_unique_obj(obj)
+struct obj *obj;
+{
+    boolean known = (obj->known || iflags.override_ID);
+
+    if (!obj->dknown && !iflags.override_ID)
+        return FALSE;
+    else if (obj->otyp == FAKE_AMULET_OF_YENDOR && !known)
+        return TRUE; /* lie */
+    else
+        return (boolean) (objects[obj->otyp].oc_unique
+                          && (known || obj->otyp == AMULET_OF_YENDOR));
+}
+
+/* should monster type be prefixed with "the"? (mostly used for corpses) */
+boolean
+the_unique_pm(ptr)
+struct permonst *ptr;
+{
+    boolean uniq;
+
+    /* even though monsters with personal names are unique, we want to
+       describe them as "Name" rather than "the Name" */
+    if (type_is_pname(ptr))
+        return FALSE;
+
+    uniq = (ptr->geno & G_UNIQ) ? TRUE : FALSE;
+    /* high priest is unique if it includes "of <deity>", otherwise not
+       (caller needs to handle the 1st possibility; we assume the 2nd);
+       worm tail should be irrelevant but is included for completeness */
+    if (ptr == &mons[PM_HIGH_PRIEST] || ptr == &mons[PM_LONG_WORM_TAIL])
+        uniq = FALSE;
+    /* Wizard no longer needs this; he's flagged as unique these days */
+    if (ptr == &mons[PM_WIZARD_OF_YENDOR])
+        uniq = TRUE;
+    return uniq;
+}
+
+STATIC_OVL void
+add_erosion_words(obj, prefix)
+struct obj *obj;
+char *prefix;
+{
+    boolean iscrys = (obj->otyp == CRYSKNIFE);
+    boolean rknown;
+
+    rknown = (iflags.override_ID == 0) ? obj->rknown : TRUE;
+
+    if (!is_damageable(obj) && !iscrys)
+        return;
+
+    /* The only cases where any of these bits do double duty are for
+     * rotted food and diluted potions, which are all not is_damageable().
+     */
+    if (obj->oeroded && !iscrys) {
+        switch (obj->oeroded) {
+        case 2:
+            Strcat(prefix, "very ");
+            break;
+        case 3:
+            Strcat(prefix, "thoroughly ");
+            break;
+        }
+        Strcat(prefix, is_rustprone(obj) ? "rusty " : "burnt ");
+    }
+    if (obj->oeroded2 && !iscrys) {
+        switch (obj->oeroded2) {
+        case 2:
+            Strcat(prefix, "very ");
+            break;
+        case 3:
+            Strcat(prefix, "thoroughly ");
+            break;
+        }
+        Strcat(prefix, is_corrodeable(obj) ? "corroded " : "rotted ");
+    }
+    if (rknown && obj->oerodeproof)
+        Strcat(prefix, iscrys
+                          ? "fixed "
+                          : is_rustprone(obj)
+                             ? "rustproof "
+                             : is_corrodeable(obj)
+                                ? "corrodeproof " /* "stainless"? */
+                                : is_flammable(obj)
+                                   ? "fireproof "
+                                   : "");
+}
+
+/* used to prevent rust on items where rust makes no difference */
+boolean
+erosion_matters(obj)
+struct obj *obj;
+{
+    switch (obj->oclass) {
+    case TOOL_CLASS:
+        /* it's possible for a rusty weptool to be polymorphed into some
+           non-weptool iron tool, in which case the rust implicitly goes
+           away, but it's also possible for it to be polymorphed into a
+           non-iron tool, in which case rust also implicitly goes away,
+           so there's no particular reason to try to handle the first
+           instance differently [this comment belongs in poly_obj()...] */
+        return is_weptool(obj) ? TRUE : FALSE;
+    case WEAPON_CLASS:
+    case ARMOR_CLASS:
+    case BALL_CLASS:
+    case CHAIN_CLASS:
+        return TRUE;
+    default:
+        break;
+    }
+    return FALSE;
+}
+
+#define DONAME_WITH_PRICE 1
+#define DONAME_VAGUE_QUAN 2
+
+STATIC_OVL char *
+doname_base(obj, doname_flags)
+struct obj *obj;
+unsigned doname_flags;
+{
+    boolean ispoisoned = FALSE,
+            with_price = (doname_flags & DONAME_WITH_PRICE) != 0,
+            vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0;
+    boolean known, dknown, cknown, bknown, lknown;
+    int omndx = obj->corpsenm;
+    char prefix[PREFIX];
+    char tmpbuf[PREFIX + 1]; /* for when we have to add something at
+                                the start of prefix instead of the
+                                end (Strcat is used on the end) */
+    register char *bp = xname(obj);
+
+    if (iflags.override_ID) {
+        known = dknown = cknown = bknown = lknown = TRUE;
+    } else {
+        known = obj->known;
+        dknown = obj->dknown;
+        cknown = obj->cknown;
+        bknown = obj->bknown;
+        lknown = obj->lknown;
+    }
+
+    /* When using xname, we want "poisoned arrow", and when using
+     * doname, we want "poisoned +0 arrow".  This kludge is about the only
+     * way to do it, at least until someone overhauls xname() and doname(),
+     * combining both into one function taking a parameter.
+     */
+    /* must check opoisoned--someone can have a weirdly-named fruit */
+    if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) {
+        bp += 9;
+        ispoisoned = TRUE;
+    }
+
+    if (obj->quan != 1L) {
+        if (dknown || !vague_quan)
+            Sprintf(prefix, "%ld ", obj->quan);
+        else
+            Strcpy(prefix, "some ");
+    } else if (obj->otyp == CORPSE) {
+        /* skip article prefix for corpses [else corpse_xname()
+           would have to be taught how to strip it off again] */
+        *prefix = '\0';
+    } else if (obj_is_pname(obj) || the_unique_obj(obj)) {
+        if (!strncmpi(bp, "the ", 4))
+            bp += 4;
+        Strcpy(prefix, "the ");
+    } else {
+        Strcpy(prefix, "a ");
+    }
+
+    /* "empty" goes at the beginning, but item count goes at the end */
+    if (cknown
+        /* bag of tricks: include "empty" prefix if it's known to
+           be empty but its precise number of charges isn't known
+           (when that is known, suffix of "(n:0)" will be appended,
+           making the prefix be redundant; note that 'known' flag
+           isn't set when emptiness gets discovered because then
+           charging magic would yield known number of new charges) */
+        && ((obj->otyp == BAG_OF_TRICKS)
+             ? (obj->spe == 0 && !obj->known)
+             /* not bag of tricks: empty if container which has no contents */
+             : ((Is_container(obj) || obj->otyp == STATUE)
+                && !Has_contents(obj))))
+        Strcat(prefix, "empty ");
+
+    if (bknown && obj->oclass != COIN_CLASS
+        && (obj->otyp != POT_WATER || !objects[POT_WATER].oc_name_known
+            || (!obj->cursed && !obj->blessed))) {
+        /* allow 'blessed clear potion' if we don't know it's holy water;
+         * always allow "uncursed potion of water"
+         */
+        if (obj->cursed)
+            Strcat(prefix, "cursed ");
+        else if (obj->blessed)
+            Strcat(prefix, "blessed ");
+        else if (!iflags.implicit_uncursed
+            /* For most items with charges or +/-, if you know how many
+             * charges are left or what the +/- is, then you must have
+             * totally identified the item, so "uncursed" is unnecessary,
+             * because an identified object not described as "blessed" or
+             * "cursed" must be uncursed.
+             *
+             * If the charges or +/- is not known, "uncursed" must be
+             * printed to avoid ambiguity between an item whose curse
+             * status is unknown, and an item known to be uncursed.
+             */
+                 || ((!known || !objects[obj->otyp].oc_charged
+                      || obj->oclass == ARMOR_CLASS
+                      || obj->oclass == RING_CLASS)
+#ifdef MAIL
+                     && obj->otyp != SCR_MAIL
+#endif
+                     && obj->otyp != FAKE_AMULET_OF_YENDOR
+                     && obj->otyp != AMULET_OF_YENDOR
+                     && !Role_if(PM_PRIEST)))
+            Strcat(prefix, "uncursed ");
+    }
+
+    if (lknown && Is_box(obj)) {
+        if (obj->obroken)
+            /* 3.6.0 used an "unlockable" prefix here but that could be
+               misunderstood to mean "capable of being unlocked" rather
+               than the intended "not capable of being locked" */
+            Strcat(bp, " with a broken lock");
+        else if (obj->olocked)
+            Strcat(prefix, "locked ");
+        else
+            Strcat(prefix, "unlocked ");
+    }
+
+    if (obj->greased)
+        Strcat(prefix, "greased ");
+
+    if (cknown && Has_contents(obj)) {
+        /* we count the number of separate stacks, which corresponds
+           to the number of inventory slots needed to be able to take
+           everything out if no merges occur */
+        long itemcount = count_contents(obj, FALSE, FALSE, TRUE);
+
+        Sprintf(eos(bp), " containing %ld item%s", itemcount,
+                plur(itemcount));
+    }
+
+    switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) {
+    case AMULET_CLASS:
+        if (obj->owornmask & W_AMUL)
+            Strcat(bp, " (being worn)");
+        break;
+    case ARMOR_CLASS:
+        if (obj->owornmask & W_ARMOR)
+            Strcat(bp, (obj == uskin) ? " (embedded in your skin)"
+                                      : " (being worn)");
+        /*FALLTHRU*/
+    case WEAPON_CLASS:
+        if (ispoisoned)
+            Strcat(prefix, "poisoned ");
+        add_erosion_words(obj, prefix);
+        if (known) {
+            Strcat(prefix, sitoa(obj->spe));
+            Strcat(prefix, " ");
+        }
+        break;
+    case TOOL_CLASS:
+        if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */
+            Strcat(bp, " (being worn)");
+            break;
+        }
+        if (obj->otyp == LEASH && obj->leashmon != 0) {
+            struct monst *mlsh = find_mid(obj->leashmon, FM_FMON);
+
+            if (!mlsh) {
+                impossible("leashed monster not on this level");
+                obj->leashmon = 0;
+            } else {
+                Sprintf(eos(bp), " (attached to %s)",
+                        a_monnam(mlsh));
+            }
+            break;
+        }
+        if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
+            if (!obj->spe)
+                Strcpy(tmpbuf, "no");
+            else
+                Sprintf(tmpbuf, "%d", obj->spe);
+            Sprintf(eos(bp), " (%s candle%s%s)", tmpbuf, plur(obj->spe),
+                    !obj->lamplit ? " attached" : ", lit");
+            break;
+        } else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP
+                   || obj->otyp == BRASS_LANTERN || Is_candle(obj)) {
+            if (Is_candle(obj)
+                && obj->age < 20L * (long) objects[obj->otyp].oc_cost)
+                Strcat(prefix, "partly used ");
+            if (obj->lamplit)
+                Strcat(bp, " (lit)");
+            break;
+        }
+        if (objects[obj->otyp].oc_charged)
+            goto charges;
+        break;
+    case WAND_CLASS:
+    charges:
+        if (known)
+            Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe);
+        break;
+    case POTION_CLASS:
+        if (obj->otyp == POT_OIL && obj->lamplit)
+            Strcat(bp, " (lit)");
+        break;
+    case RING_CLASS:
+    ring:
+        if (obj->owornmask & W_RINGR)
+            Strcat(bp, " (on right ");
+        if (obj->owornmask & W_RINGL)
+            Strcat(bp, " (on left ");
+        if (obj->owornmask & W_RING) {
+            Strcat(bp, body_part(HAND));
+            Strcat(bp, ")");
+        }
+        if (known && objects[obj->otyp].oc_charged) {
+            Strcat(prefix, sitoa(obj->spe));
+            Strcat(prefix, " ");
+        }
+        break;
+    case FOOD_CLASS:
+        if (obj->oeaten)
+            Strcat(prefix, "partly eaten ");
+        if (obj->otyp == CORPSE) {
+            /* (quan == 1) => want corpse_xname() to supply article,
+               (quan != 1) => already have count or "some" as prefix;
+               "corpse" is already in the buffer returned by xname() */
+            unsigned cxarg = (((obj->quan != 1L) ? 0 : CXN_ARTICLE)
+                              | CXN_NOCORPSE);
+            char *cxstr = corpse_xname(obj, prefix, cxarg);
+
+            Sprintf(prefix, "%s ", cxstr);
+            /* avoid having doname(corpse) consume an extra obuf */
+            releaseobuf(cxstr);
+        } else if (obj->otyp == EGG) {
+#if 0 /* corpses don't tell if they're stale either */
+            if (known && stale_egg(obj))
+                Strcat(prefix, "stale ");
+#endif
+            if (omndx >= LOW_PM
+                && (known || (mvitals[omndx].mvflags & MV_KNOWS_EGG))) {
+                Strcat(prefix, mons[omndx].mname);
+                Strcat(prefix, " ");
+                if (obj->spe)
+                    Strcat(bp, " (laid by you)");
+            }
+        }
+        if (obj->otyp == MEAT_RING)
+            goto ring;
+        break;
+    case BALL_CLASS:
+    case CHAIN_CLASS:
+        add_erosion_words(obj, prefix);
+        if (obj->owornmask & W_BALL)
+            Strcat(bp, " (chained to you)");
+        break;
+    }
+
+    if ((obj->owornmask & W_WEP) && !mrg_to_wielded) {
+        if (obj->quan != 1L) {
+            Strcat(bp, " (wielded)");
+        } else {
+            const char *hand_s = body_part(HAND);
+
+            if (bimanual(obj))
+                hand_s = makeplural(hand_s);
+            Sprintf(eos(bp), " (weapon in %s)", hand_s);
+
+            if (warn_obj_cnt && obj == uwep && (EWarn_of_mon & W_WEP) != 0L) {
+                /* presumably can be felt when blind */
+                Strcat(bp, " (glowing");
+                if (!Blind)
+                    Sprintf(eos(bp), " %s", glow_color(obj->oartifact));
+                Strcat(bp, ")");
+            }
+        }
+    }
+    if (obj->owornmask & W_SWAPWEP) {
+        if (u.twoweap)
+            Sprintf(eos(bp), " (wielded in other %s)", body_part(HAND));
+        else
+            Strcat(bp, " (alternate weapon; not wielded)");
+    }
+    if (obj->owornmask & W_QUIVER) {
+        switch (obj->oclass) {
+        case WEAPON_CLASS:
+            if (is_ammo(obj)) {
+                if (objects[obj->otyp].oc_skill == -P_BOW) {
+                    /* Ammo for a bow */
+                    Strcat(bp, " (in quiver)");
+                    break;
+                } else {
+                    /* Ammo not for a bow */
+                    Strcat(bp, " (in quiver pouch)");
+                    break;
+                }
+            } else {
+                /* Weapons not considered ammo */
+                Strcat(bp, " (at the ready)");
+                break;
+            }
+        /* Small things and ammo not for a bow */
+        case RING_CLASS:
+        case AMULET_CLASS:
+        case WAND_CLASS:
+        case COIN_CLASS:
+        case GEM_CLASS:
+            Strcat(bp, " (in quiver pouch)");
+            break;
+        default: /* odd things */
+            Strcat(bp, " (at the ready)");
+        }
+    }
+    if (!iflags.suppress_price && is_unpaid(obj)) {
+        long quotedprice = unpaid_cost(obj, TRUE);
+
+        Sprintf(eos(bp), " (%s, %ld %s)",
+                obj->unpaid ? "unpaid" : "contents",
+                quotedprice, currency(quotedprice));
+    } else if (with_price) {
+        long price = get_cost_of_shop_item(obj);
+
+        if (price > 0)
+            Sprintf(eos(bp), " (%ld %s)", price, currency(price));
+    }
+    if (!strncmp(prefix, "a ", 2)
+        && index(vowels, *(prefix + 2) ? *(prefix + 2) : *bp)
+        && (*(prefix + 2)
+            || (strncmp(bp, "uranium", 7) && strncmp(bp, "unicorn", 7)
+                && strncmp(bp, "eucalyptus", 10)))) {
+        Strcpy(tmpbuf, prefix);
+        Strcpy(prefix, "an ");
+        Strcpy(prefix + 3, tmpbuf + 2);
+    }
+
+    /* show weight for items (debug tourist info)
+     * aum is stolen from Crawl's "Arbitrary Unit of Measure" */
+    if (wizard && iflags.wizweight) {
+        Sprintf(eos(bp), " (%d aum)", obj->owt);
+    }
+    bp = strprepend(bp, prefix);
+    return bp;
+}
+
+char *
+doname(obj)
+struct obj *obj;
+{
+    return doname_base(obj, (unsigned) 0);
+}
+
+/* Name of object including price. */
+char *
+doname_with_price(obj)
+struct obj *obj;
+{
+    return doname_base(obj, DONAME_WITH_PRICE);
+}
+
+/* "some" instead of precise quantity if obj->dknown not set */
+char *
+doname_vague_quan(obj)
+struct obj *obj;
+{
+    /* Used by farlook.
+     * If it hasn't been seen up close and quantity is more than one,
+     * use "some" instead of the quantity: "some gold pieces" rather
+     * than "25 gold pieces".  This is suboptimal, to put it mildly,
+     * because lookhere and pickup report the precise amount.
+     * Picking the item up while blind also shows the precise amount
+     * for inventory display, then dropping it while still blind leaves
+     * obj->dknown unset so the count reverts to "some" for farlook.
+     *
+     * TODO: add obj->qknown flag for 'quantity known' on stackable
+     * items; it could overlay obj->cknown since no containers stack.
+     */
+    return doname_base(obj, DONAME_VAGUE_QUAN);
+}
+
+/* used from invent.c */
+boolean
+not_fully_identified(otmp)
+struct obj *otmp;
+{
+    /* gold doesn't have any interesting attributes [yet?] */
+    if (otmp->oclass == COIN_CLASS)
+        return FALSE; /* always fully ID'd */
+    /* check fundamental ID hallmarks first */
+    if (!otmp->known || !otmp->dknown
+#ifdef MAIL
+        || (!otmp->bknown && otmp->otyp != SCR_MAIL)
+#else
+        || !otmp->bknown
+#endif
+        || !objects[otmp->otyp].oc_name_known)
+        return TRUE;
+    if ((!otmp->cknown && (Is_container(otmp) || otmp->otyp == STATUE))
+        || (!otmp->lknown && Is_box(otmp)))
+        return TRUE;
+    if (otmp->oartifact && undiscovered_artifact(otmp->oartifact))
+        return TRUE;
+    /* otmp->rknown is the only item of interest if we reach here */
+    /*
+     *  Note:  if a revision ever allows scrolls to become fireproof or
+     *  rings to become shockproof, this checking will need to be revised.
+     *  `rknown' ID only matters if xname() will provide the info about it.
+     */
+    if (otmp->rknown
+        || (otmp->oclass != ARMOR_CLASS && otmp->oclass != WEAPON_CLASS
+            && !is_weptool(otmp)            /* (redundant) */
+            && otmp->oclass != BALL_CLASS)) /* (useless) */
+        return FALSE;
+    else /* lack of `rknown' only matters for vulnerable objects */
+        return (boolean) (is_rustprone(otmp) || is_corrodeable(otmp)
+                          || is_flammable(otmp));
+}
+
+/* format a corpse name (xname() omits monster type; doname() calls us);
+   eatcorpse() also uses us for death reason when eating tainted glob */
+char *
+corpse_xname(otmp, adjective, cxn_flags)
+struct obj *otmp;
+const char *adjective;
+unsigned cxn_flags; /* bitmask of CXN_xxx values */
+{
+    char *nambuf = nextobuf();
+    int omndx = otmp->corpsenm;
+    boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0,
+            /* suppress "the" from "the unique monster corpse" */
+        no_prefix = (cxn_flags & CXN_NO_PFX) != 0,
+            /* include "the" for "the woodchuck corpse */
+        the_prefix = (cxn_flags & CXN_PFX_THE) != 0,
+            /* include "an" for "an ogre corpse */
+        any_prefix = (cxn_flags & CXN_ARTICLE) != 0,
+            /* leave off suffix (do_name() appends "corpse" itself) */
+        omit_corpse = (cxn_flags & CXN_NOCORPSE) != 0,
+        possessive = FALSE,
+        glob = (otmp->otyp != CORPSE && otmp->globby);
+    const char *mname;
+
+    if (glob) {
+        mname = OBJ_NAME(objects[otmp->otyp]); /* "glob of <monster>" */
+    } else if (omndx == NON_PM) { /* paranoia */
+        mname = "thing";
+        /* [Possible enhancement:  check whether corpse has monster traits
+            attached in order to use priestname() for priests and minions.] */
+    } else if (omndx == PM_ALIGNED_PRIEST) {
+        /* avoid "aligned priest"; it just exposes internal details */
+        mname = "priest";
+    } else {
+        mname = mons[omndx].mname;
+        if (the_unique_pm(&mons[omndx]) || type_is_pname(&mons[omndx])) {
+            mname = s_suffix(mname);
+            possessive = TRUE;
+            /* don't precede personal name like "Medusa" with an article */
+            if (type_is_pname(&mons[omndx]))
+                no_prefix = TRUE;
+            /* always precede non-personal unique monster name like
+               "Oracle" with "the" unless explicitly overridden */
+            else if (the_unique_pm(&mons[omndx]) && !no_prefix)
+                the_prefix = TRUE;
+        }
+    }
+    if (no_prefix)
+        the_prefix = any_prefix = FALSE;
+    else if (the_prefix)
+        any_prefix = FALSE; /* mutually exclusive */
+
+    *nambuf = '\0';
+    /* can't use the() the way we use an() below because any capitalized
+       Name causes it to assume a personal name and return Name as-is;
+       that's usually the behavior wanted, but here we need to force "the"
+       to precede capitalized unique monsters (pnames are handled above) */
+    if (the_prefix)
+        Strcat(nambuf, "the ");
+
+    if (!adjective || !*adjective) {
+        /* normal case:  newt corpse */
+        Strcat(nambuf, mname);
+    } else {
+        /* adjective positioning depends upon format of monster name */
+        if (possessive) /* Medusa's cursed partly eaten corpse */
+            Sprintf(eos(nambuf), "%s %s", mname, adjective);
+        else /* cursed partly eaten troll corpse */
+            Sprintf(eos(nambuf), "%s %s", adjective, mname);
+        /* in case adjective has a trailing space, squeeze it out */
+        mungspaces(nambuf);
+        /* doname() might include a count in the adjective argument;
+           if so, don't prepend an article */
+        if (digit(*adjective))
+            any_prefix = FALSE;
+    }
+
+    if (glob) {
+        ; /* omit_corpse doesn't apply; quantity is always 1 */
+    } else if (!omit_corpse) {
+        Strcat(nambuf, " corpse");
+        /* makeplural(nambuf) => append "s" to "corpse" */
+        if (otmp->quan > 1L && !ignore_quan) {
+            Strcat(nambuf, "s");
+            any_prefix = FALSE; /* avoid "a newt corpses" */
+        }
+    }
+
+    /* it's safe to overwrite our nambuf after an() has copied
+       its old value into another buffer */
+    if (any_prefix)
+        Strcpy(nambuf, an(nambuf));
+
+    return nambuf;
+}
+
+/* xname doesn't include monster type for "corpse"; cxname does */
+char *
+cxname(obj)
+struct obj *obj;
+{
+    if (obj->otyp == CORPSE)
+        return corpse_xname(obj, (const char *) 0, CXN_NORMAL);
+    return xname(obj);
+}
+
+/* like cxname, but ignores quantity */
+char *
+cxname_singular(obj)
+struct obj *obj;
+{
+    if (obj->otyp == CORPSE)
+        return corpse_xname(obj, (const char *) 0, CXN_SINGULAR);
+    return xname_flags(obj, CXN_SINGULAR);
+}
+
+/* treat an object as fully ID'd when it might be used as reason for death */
+char *
+killer_xname(obj)
+struct obj *obj;
+{
+    struct obj save_obj;
+    unsigned save_ocknown;
+    char *buf, *save_ocuname, *save_oname = (char *) 0;
+
+    /* bypass object twiddling for artifacts */
+    if (obj->oartifact)
+        return bare_artifactname(obj);
+
+    /* remember original settings for core of the object;
+       oextra structs other than oname don't matter here--since they
+       aren't modified they don't need to be saved and restored */
+    save_obj = *obj;
+    if (has_oname(obj))
+        save_oname = ONAME(obj);
+
+    /* killer name should be more specific than general xname; however, exact
+       info like blessed/cursed and rustproof makes things be too verbose */
+    obj->known = obj->dknown = 1;
+    obj->bknown = obj->rknown = obj->greased = 0;
+    /* if character is a priest[ess], bknown will get toggled back on */
+    if (obj->otyp != POT_WATER)
+        obj->blessed = obj->cursed = 0;
+    else
+        obj->bknown = 1; /* describe holy/unholy water as such */
+    /* "killed by poisoned <obj>" would be misleading when poison is
+       not the cause of death and "poisoned by poisoned <obj>" would
+       be redundant when it is, so suppress "poisoned" prefix */
+    obj->opoisoned = 0;
+    /* strip user-supplied name; artifacts keep theirs */
+    if (!obj->oartifact && save_oname)
+        ONAME(obj) = (char *) 0;
+    /* temporarily identify the type of object */
+    save_ocknown = objects[obj->otyp].oc_name_known;
+    objects[obj->otyp].oc_name_known = 1;
+    save_ocuname = objects[obj->otyp].oc_uname;
+    objects[obj->otyp].oc_uname = 0; /* avoid "foo called bar" */
+
+    /* format the object */
+    if (obj->otyp == CORPSE) {
+        buf = nextobuf();
+        Strcpy(buf, corpse_xname(obj, (const char *) 0, CXN_NORMAL));
+    } else if (obj->otyp == SLIME_MOLD) {
+        /* concession to "most unique deaths competition" in the annual
+           devnull tournament, suppress player supplied fruit names because
+           those can be used to fake other objects and dungeon features */
+        buf = nextobuf();
+        Sprintf(buf, "deadly slime mold%s", plur(obj->quan));
+    } else {
+        buf = xname(obj);
+    }
+    /* apply an article if appropriate; caller should always use KILLED_BY */
+    if (obj->quan == 1L && !strstri(buf, "'s ") && !strstri(buf, "s' "))
+        buf = (obj_is_pname(obj) || the_unique_obj(obj)) ? the(buf) : an(buf);
+
+    objects[obj->otyp].oc_name_known = save_ocknown;
+    objects[obj->otyp].oc_uname = save_ocuname;
+    *obj = save_obj; /* restore object's core settings */
+    if (!obj->oartifact && save_oname)
+        ONAME(obj) = save_oname;
+
+    return buf;
+}
+
+/* xname,doname,&c with long results reformatted to omit some stuff */
+char *
+short_oname(obj, func, altfunc, lenlimit)
+struct obj *obj;
+char *FDECL((*func), (OBJ_P)),    /* main formatting routine */
+     *FDECL((*altfunc), (OBJ_P)); /* alternate for shortest result */
+unsigned lenlimit;
+{
+    struct obj save_obj;
+    char unamebuf[12], onamebuf[12], *save_oname, *save_uname, *outbuf;
+
+    outbuf = (*func)(obj);
+    if ((unsigned) strlen(outbuf) <= lenlimit)
+        return outbuf;
+
+    /* shorten called string to fairly small amount */
+    save_uname = objects[obj->otyp].oc_uname;
+    if (save_uname && strlen(save_uname) >= sizeof unamebuf) {
+        (void) strncpy(unamebuf, save_uname, sizeof unamebuf - 4);
+        Strcpy(unamebuf + sizeof unamebuf - 4, "...");
+        objects[obj->otyp].oc_uname = unamebuf;
+        releaseobuf(outbuf);
+        outbuf = (*func)(obj);
+        objects[obj->otyp].oc_uname = save_uname; /* restore called string */
+        if ((unsigned) strlen(outbuf) <= lenlimit)
+            return outbuf;
+    }
+
+    /* shorten named string to fairly small amount */
+    save_oname = has_oname(obj) ? ONAME(obj) : 0;
+    if (save_oname && strlen(save_oname) >= sizeof onamebuf) {
+        (void) strncpy(onamebuf, save_oname, sizeof onamebuf - 4);
+        Strcpy(onamebuf + sizeof onamebuf - 4, "...");
+        ONAME(obj) = onamebuf;
+        releaseobuf(outbuf);
+        outbuf = (*func)(obj);
+        ONAME(obj) = save_oname; /* restore named string */
+        if ((unsigned) strlen(outbuf) <= lenlimit)
+            return outbuf;
+    }
+
+    /* shorten both called and named strings;
+       unamebuf and onamebuf have both already been populated */
+    if (save_uname && strlen(save_uname) >= sizeof unamebuf && save_oname
+        && strlen(save_oname) >= sizeof onamebuf) {
+        objects[obj->otyp].oc_uname = unamebuf;
+        ONAME(obj) = onamebuf;
+        releaseobuf(outbuf);
+        outbuf = (*func)(obj);
+        if ((unsigned) strlen(outbuf) <= lenlimit) {
+            objects[obj->otyp].oc_uname = save_uname;
+            ONAME(obj) = save_oname;
+            return outbuf;
+        }
+    }
+
+    /* still long; strip several name-lengthening attributes;
+       called and named strings are still in truncated form */
+    save_obj = *obj;
+    obj->bknown = obj->rknown = obj->greased = 0;
+    obj->oeroded = obj->oeroded2 = 0;
+    releaseobuf(outbuf);
+    outbuf = (*func)(obj);
+    if (altfunc && (unsigned) strlen(outbuf) > lenlimit) {
+        /* still long; use the alternate function (usually one of
+           the jackets around minimal_xname()) */
+        releaseobuf(outbuf);
+        outbuf = (*altfunc)(obj);
+    }
+    /* restore the object */
+    *obj = save_obj;
+    if (save_oname)
+        ONAME(obj) = save_oname;
+    if (save_uname)
+        objects[obj->otyp].oc_uname = save_uname;
+
+    /* use whatever we've got, whether it's too long or not */
+    return outbuf;
+}
+
+/*
+ * Used if only one of a collection of objects is named (e.g. in eat.c).
+ */
+const char *
+singular(otmp, func)
+register struct obj *otmp;
+char *FDECL((*func), (OBJ_P));
+{
+    long savequan;
+    char *nam;
+
+    /* using xname for corpses does not give the monster type */
+    if (otmp->otyp == CORPSE && func == xname)
+        func = cxname;
+
+    savequan = otmp->quan;
+    otmp->quan = 1L;
+    nam = (*func)(otmp);
+    otmp->quan = savequan;
+    return nam;
+}
+
+char *
+an(str)
+register const char *str;
+{
+    char *buf = nextobuf();
+
+    buf[0] = '\0';
+
+    if (strncmpi(str, "the ", 4) && strcmp(str, "molten lava")
+        && strcmp(str, "iron bars") && strcmp(str, "ice")) {
+        if (index(vowels, *str) && strncmp(str, "one-", 4)
+            && strncmp(str, "useful", 6) && strncmp(str, "unicorn", 7)
+            && strncmp(str, "uranium", 7) && strncmp(str, "eucalyptus", 10))
+            Strcpy(buf, "an ");
+        else
+            Strcpy(buf, "a ");
+    }
+
+    Strcat(buf, str);
+    return buf;
+}
+
+char *
+An(str)
+const char *str;
+{
+    char *tmp = an(str);
+
+    *tmp = highc(*tmp);
+    return tmp;
+}
+
+/*
+ * Prepend "the" if necessary; assumes str is a subject derived from xname.
+ * Use type_is_pname() for monster names, not the().  the() is idempotent.
+ */
+char *
+the(str)
+const char *str;
+{
+    char *buf = nextobuf();
+    boolean insert_the = FALSE;
+
+    if (!strncmpi(str, "the ", 4)) {
+        buf[0] = lowc(*str);
+        Strcpy(&buf[1], str + 1);
+        return buf;
+    } else if (*str < 'A' || *str > 'Z'
+               /* 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)) {
+        /* not a proper name, needs an article */
+        insert_the = TRUE;
+    } else {
+        /* Probably a proper name, might not need an article */
+        register char *tmp, *named, *called;
+        int l;
+
+        /* some objects have capitalized adjectives in their names */
+        if (((tmp = rindex(str, ' ')) != 0 || (tmp = rindex(str, '-')) != 0)
+            && (tmp[1] < 'A' || tmp[1] > 'Z')) {
+            insert_the = TRUE;
+        } else if (tmp && index(str, ' ') < tmp) { /* has spaces */
+            /* it needs an article if the name contains "of" */
+            tmp = strstri(str, " of ");
+            named = strstri(str, " named ");
+            called = strstri(str, " called ");
+            if (called && (!named || called < named))
+                named = called;
+
+            if (tmp && (!named || tmp < named)) /* found an "of" */
+                insert_the = TRUE;
+            /* stupid special case: lacks "of" but needs "the" */
+            else if (!named && (l = strlen(str)) >= 31
+                     && !strcmp(&str[l - 31],
+                                "Platinum Yendorian Express Card"))
+                insert_the = TRUE;
+        }
+    }
+    if (insert_the)
+        Strcpy(buf, "the ");
+    else
+        buf[0] = '\0';
+    Strcat(buf, str);
+
+    return buf;
+}
+
+char *
+The(str)
+const char *str;
+{
+    char *tmp = the(str);
+
+    *tmp = highc(*tmp);
+    return tmp;
+}
+
+/* returns "count cxname(otmp)" or just cxname(otmp) if count == 1 */
+char *
+aobjnam(otmp, verb)
+struct obj *otmp;
+const char *verb;
+{
+    char prefix[PREFIX];
+    char *bp = cxname(otmp);
+
+    if (otmp->quan != 1L) {
+        Sprintf(prefix, "%ld ", otmp->quan);
+        bp = strprepend(bp, prefix);
+    }
+    if (verb) {
+        Strcat(bp, " ");
+        Strcat(bp, otense(otmp, verb));
+    }
+    return bp;
+}
+
+/* combine yname and aobjnam eg "your count cxname(otmp)" */
+char *
+yobjnam(obj, verb)
+struct obj *obj;
+const char *verb;
+{
+    char *s = aobjnam(obj, verb);
+
+    /* leave off "your" for most of your artifacts, but prepend
+     * "your" for unique objects and "foo of bar" quest artifacts */
+    if (!carried(obj) || !obj_is_pname(obj)
+        || obj->oartifact >= ART_ORB_OF_DETECTION) {
+        char *outbuf = shk_your(nextobuf(), obj);
+        int space_left = BUFSZ - 1 - strlen(outbuf);
+
+        s = strncat(outbuf, s, space_left);
+    }
+    return s;
+}
+
+/* combine Yname2 and aobjnam eg "Your count cxname(otmp)" */
+char *
+Yobjnam2(obj, verb)
+struct obj *obj;
+const char *verb;
+{
+    register char *s = yobjnam(obj, verb);
+
+    *s = highc(*s);
+    return s;
+}
+
+/* like aobjnam, but prepend "The", not count, and use xname */
+char *
+Tobjnam(otmp, verb)
+struct obj *otmp;
+const char *verb;
+{
+    char *bp = The(xname(otmp));
+
+    if (verb) {
+        Strcat(bp, " ");
+        Strcat(bp, otense(otmp, verb));
+    }
+    return bp;
+}
+
+/* capitalized variant of doname() */
+char *
+Doname2(obj)
+struct obj *obj;
+{
+    char *s = doname(obj);
+
+    *s = highc(*s);
+    return s;
+}
+
+/* returns "[your ]xname(obj)" or "Foobar's xname(obj)" or "the xname(obj)" */
+char *
+yname(obj)
+struct obj *obj;
+{
+    char *s = cxname(obj);
+
+    /* leave off "your" for most of your artifacts, but prepend
+     * "your" for unique objects and "foo of bar" quest artifacts */
+    if (!carried(obj) || !obj_is_pname(obj)
+        || obj->oartifact >= ART_ORB_OF_DETECTION) {
+        char *outbuf = shk_your(nextobuf(), obj);
+        int space_left = BUFSZ - 1 - strlen(outbuf);
+
+        s = strncat(outbuf, s, space_left);
+    }
+
+    return s;
+}
+
+/* capitalized variant of yname() */
+char *
+Yname2(obj)
+struct obj *obj;
+{
+    char *s = yname(obj);
+
+    *s = highc(*s);
+    return s;
+}
+
+/* returns "your minimal_xname(obj)"
+ * or "Foobar's minimal_xname(obj)"
+ * or "the minimal_xname(obj)"
+ */
+char *
+ysimple_name(obj)
+struct obj *obj;
+{
+    char *outbuf = nextobuf();
+    char *s = shk_your(outbuf, obj); /* assert( s == outbuf ); */
+    int space_left = BUFSZ - 1 - strlen(s);
+
+    return strncat(s, minimal_xname(obj), space_left);
+}
+
+/* capitalized variant of ysimple_name() */
+char *
+Ysimple_name2(obj)
+struct obj *obj;
+{
+    char *s = ysimple_name(obj);
+
+    *s = highc(*s);
+    return s;
+}
+
+/* "scroll" or "scrolls" */
+char *
+simpleonames(obj)
+struct obj *obj;
+{
+    char *simpleoname = minimal_xname(obj);
+
+    if (obj->quan != 1L)
+        simpleoname = makeplural(simpleoname);
+    return simpleoname;
+}
+
+/* "a scroll" or "scrolls"; "a silver bell" or "the Bell of Opening" */
+char *
+ansimpleoname(obj)
+struct obj *obj;
+{
+    char *simpleoname = simpleonames(obj);
+    int otyp = obj->otyp;
+
+    /* prefix with "the" if a unique item, or a fake one imitating same,
+       has been formatted with its actual name (we let typename() handle
+       any `known' and `dknown' checking necessary) */
+    if (otyp == FAKE_AMULET_OF_YENDOR)
+        otyp = AMULET_OF_YENDOR;
+    if (objects[otyp].oc_unique
+        && !strcmp(simpleoname, OBJ_NAME(objects[otyp])))
+        return the(simpleoname);
+
+    /* simpleoname is singular if quan==1, plural otherwise */
+    if (obj->quan == 1L)
+        simpleoname = an(simpleoname);
+    return simpleoname;
+}
+
+/* "the scroll" or "the scrolls" */
+char *
+thesimpleoname(obj)
+struct obj *obj;
+{
+    char *simpleoname = simpleonames(obj);
+
+    return the(simpleoname);
+}
+
+/* artifact's name without any object type or known/dknown/&c feedback */
+char *
+bare_artifactname(obj)
+struct obj *obj;
+{
+    char *outbuf;
+
+    if (obj->oartifact) {
+        outbuf = nextobuf();
+        Strcpy(outbuf, artiname(obj->oartifact));
+        if (!strncmp(outbuf, "The ", 4))
+            outbuf[0] = lowc(outbuf[0]);
+    } else {
+        outbuf = xname(obj);
+    }
+    return outbuf;
+}
+
+static const char *wrp[] = {
+    "wand",   "ring",      "potion",     "scroll", "gem",
+    "amulet", "spellbook", "spell book",
+    /* for non-specific wishes */
+    "weapon", "armor",     "tool",       "food",   "comestible",
+};
+static const char wrpsym[] = { WAND_CLASS,   RING_CLASS,   POTION_CLASS,
+                               SCROLL_CLASS, GEM_CLASS,    AMULET_CLASS,
+                               SPBOOK_CLASS, SPBOOK_CLASS, WEAPON_CLASS,
+                               ARMOR_CLASS,  TOOL_CLASS,   FOOD_CLASS,
+                               FOOD_CLASS };
+
+/* return form of the verb (input plural) if xname(otmp) were the subject */
+char *
+otense(otmp, verb)
+struct obj *otmp;
+const char *verb;
+{
+    char *buf;
+
+    /*
+     * verb is given in plural (without trailing s).  Return as input
+     * if the result of xname(otmp) would be plural.  Don't bother
+     * recomputing xname(otmp) at this time.
+     */
+    if (!is_plural(otmp))
+        return vtense((char *) 0, verb);
+
+    buf = nextobuf();
+    Strcpy(buf, verb);
+    return buf;
+}
+
+/* various singular words that vtense would otherwise categorize as plural;
+   also used by makesingular() to catch some special cases */
+static const char *const special_subjs[] = {
+    "erinys",  "manes", /* this one is ambiguous */
+    "Cyclops", "Hippocrates",     "Pelias",    "aklys",
+    "amnesia", "detect monsters", "paralysis", "shape changers",
+    "nemesis", 0
+    /* note: "detect monsters" and "shape changers" are normally
+       caught via "<something>(s) of <whatever>", but they can be
+       wished for using the shorter form, so we include them here
+       to accommodate usage by makesingular during wishing */
+};
+
+/* return form of the verb (input plural) for present tense 3rd person subj */
+char *
+vtense(subj, verb)
+register const char *subj;
+register const char *verb;
+{
+    char *buf = nextobuf(), *bspot;
+    int len, ltmp;
+    const char *sp, *spot;
+    const char *const *spec;
+
+    /*
+     * verb is given in plural (without trailing s).  Return as input
+     * if subj appears to be plural.  Add special cases as necessary.
+     * Many hard cases can already be handled by using otense() instead.
+     * If this gets much bigger, consider decomposing makeplural.
+     * Note: monster names are not expected here (except before corpse).
+     *
+     * Special case: allow null sobj to get the singular 3rd person
+     * present tense form so we don't duplicate this code elsewhere.
+     */
+    if (subj) {
+        if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3))
+            goto sing;
+        spot = (const char *) 0;
+        for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) {
+            if (!strncmpi(sp, " of ", 4) || !strncmpi(sp, " from ", 6)
+                || !strncmpi(sp, " called ", 8) || !strncmpi(sp, " named ", 7)
+                || !strncmpi(sp, " labeled ", 9)) {
+                if (sp != subj)
+                    spot = sp - 1;
+                break;
+            }
+        }
+        len = (int) strlen(subj);
+        if (!spot)
+            spot = subj + len - 1;
+
+        /*
+         * plural: anything that ends in 's', but not '*us' or '*ss'.
+         * Guess at a few other special cases that makeplural creates.
+         */
+        if ((lowc(*spot) == 's' && spot != subj
+             && !index("us", lowc(*(spot - 1))))
+            || !BSTRNCMPI(subj, spot - 3, "eeth", 4)
+            || !BSTRNCMPI(subj, spot - 3, "feet", 4)
+            || !BSTRNCMPI(subj, spot - 1, "ia", 2)
+            || !BSTRNCMPI(subj, spot - 1, "ae", 2)) {
+            /* check for special cases to avoid false matches */
+            len = (int) (spot - subj) + 1;
+            for (spec = special_subjs; *spec; spec++) {
+                ltmp = strlen(*spec);
+                if (len == ltmp && !strncmpi(*spec, subj, len))
+                    goto sing;
+                /* also check for <prefix><space><special_subj>
+                   to catch things like "the invisible erinys" */
+                if (len > ltmp && *(spot - ltmp) == ' '
+                    && !strncmpi(*spec, spot - ltmp + 1, ltmp))
+                    goto sing;
+            }
+
+            return strcpy(buf, verb);
+        }
+        /*
+         * 3rd person plural doesn't end in telltale 's';
+         * 2nd person singular behaves as if plural.
+         */
+        if (!strcmpi(subj, "they") || !strcmpi(subj, "you"))
+            return strcpy(buf, verb);
+    }
+
+sing:
+    Strcpy(buf, verb);
+    len = (int) strlen(buf);
+    bspot = buf + len - 1;
+
+    if (!strcmpi(buf, "are")) {
+        Strcasecpy(buf, "is");
+    } else if (!strcmpi(buf, "have")) {
+        Strcasecpy(bspot - 1, "s");
+    } else if (index("zxs", lowc(*bspot))
+               || (len >= 2 && lowc(*bspot) == 'h'
+                   && index("cs", lowc(*(bspot - 1))))
+               || (len == 2 && lowc(*bspot) == 'o')) {
+        /* Ends in z, x, s, ch, sh; add an "es" */
+        Strcasecpy(bspot + 1, "es");
+    } else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot - 1)))) {
+        /* like "y" case in makeplural */
+        Strcasecpy(bspot, "ies");
+    } else {
+        Strcasecpy(bspot + 1, "s");
+    }
+
+    return buf;
+}
+
+struct sing_plur {
+    const char *sing, *plur;
+};
+
+/* word pairs that don't fit into formula-based transformations;
+   also some suffices which have very few--often one--matches or
+   which aren't systematically reversible (knives, staves) */
+static struct sing_plur one_off[] = {
+    { "child",
+      "children" },      /* (for wise guys who give their food funny names) */
+    { "cubus", "cubi" }, /* in-/suc-cubus */
+    { "culus", "culi" }, /* homunculus */
+    { "djinni", "djinn" },
+    { "erinys", "erinyes" },
+    { "foot", "feet" },
+    { "fungus", "fungi" },
+    { "goose", "geese" },
+    { "knife", "knives" },
+    { "labrum", "labra" }, /* candelabrum */
+    { "louse", "lice" },
+    { "mouse", "mice" },
+    { "mumak", "mumakil" },
+    { "nemesis", "nemeses" },
+    { "ovum", "ova" },
+    { "ox", "oxen" },
+    { "passerby", "passersby" },
+    { "rtex", "rtices" }, /* vortex */
+    { "serum", "sera" },
+    { "staff", "staves" },
+    { "tooth", "teeth" },
+    { 0, 0 }
+};
+
+static const char *const as_is[] = {
+    /* makesingular() leaves these plural due to how they're used */
+    "boots",   "shoes",     "gloves",    "lenses",   "scales",
+    "eyes",    "gauntlets", "iron bars",
+    /* both singular and plural are spelled the same */
+    "bison",   "deer",      "elk",       "fish",      "fowl",
+    "tuna",    "yaki",      "-hai",      "krill",     "manes",
+    "moose",   "ninja",     "sheep",     "ronin",     "roshi",
+    "shito",   "tengu",     "ki-rin",    "Nazgul",    "gunyoki",
+    "piranha", "samurai",   "shuriken", 0,
+    /* Note:  "fish" and "piranha" are collective plurals, suitable
+       for "wiped out all <foo>".  For "3 <foo>", they should be
+       "fishes" and "piranhas" instead.  We settle for collective
+       variant instead of attempting to support both. */
+};
+
+/* singularize/pluralize decisions common to both makesingular & makeplural */
+STATIC_OVL boolean
+singplur_lookup(basestr, endstring, to_plural, alt_as_is)
+char *basestr, *endstring;    /* base string, pointer to eos(string) */
+boolean to_plural;            /* true => makeplural, false => makesingular */
+const char *const *alt_as_is; /* another set like as_is[] */
+{
+    const struct sing_plur *sp;
+    const char *same, *other, *const *as;
+    int al;
+
+    for (as = as_is; *as; ++as) {
+        al = (int) strlen(*as);
+        if (!BSTRCMPI(basestr, endstring - al, *as))
+            return TRUE;
+    }
+    if (alt_as_is) {
+        for (as = alt_as_is; *as; ++as) {
+            al = (int) strlen(*as);
+            if (!BSTRCMPI(basestr, endstring - al, *as))
+                return TRUE;
+        }
+    }
+
+    /* avoid false hit on one_off[].plur == "lice" or .sing == "goose";
+       if more of these turn up, one_off[] entries will need to flagged
+       as to which are whole words and which are matchable as suffices
+       then matching in the loop below will end up becoming more complex */
+    if (!strcmpi(basestr, "slice")
+        || !strcmpi(basestr, "mongoose")) {
+        if (to_plural)
+            Strcasecpy(endstring, "s");
+        return TRUE;
+    }
+    /* skip "ox" -> "oxen" entry when pluralizing "<something>ox"
+       unless it is muskox */
+    if (to_plural && strlen(basestr) > 2 && !strcmpi(endstring - 2, "ox")
+        && strcmpi(endstring - 6, "muskox")) {
+        /* "fox" -> "foxes" */
+        Strcasecpy(endstring, "es");
+        return TRUE;
+    }
+    if (to_plural) {
+        if (!strcmpi(endstring - 3, "man")
+            && badman(basestr, to_plural)) {
+            Strcasecpy(endstring, "s");
+            return TRUE;
+        }
+    } else {
+        if (!strcmpi(endstring - 3, "men")
+            && badman(basestr, to_plural))
+            return TRUE;
+    }
+    for (sp = one_off; sp->sing; sp++) {
+        /* check whether endstring already matches */
+        same = to_plural ? sp->plur : sp->sing;
+        al = (int) strlen(same);
+        if (!BSTRCMPI(basestr, endstring - al, same))
+            return TRUE; /* use as-is */
+        /* check whether it matches the inverse; if so, transform it */
+        other = to_plural ? sp->sing : sp->plur;
+        al = (int) strlen(other);
+        if (!BSTRCMPI(basestr, endstring - al, other)) {
+            Strcasecpy(endstring - al, same);
+            return TRUE; /* one_off[] transformation */
+        }
+    }
+    return FALSE;
+}
+
+/* searches for common compounds, ex. lump of royal jelly */
+STATIC_OVL char *
+singplur_compound(str)
+char *str;
+{
+    /* if new entries are added, be sure to keep compound_start[] in sync */
+    static const char *const compounds[] =
+        {
+          " of ",     " labeled ", " called ",
+          " named ",  " above", /* lurkers above */
+          " versus ", " from ",    " in ",
+          " on ",     " a la ",    " with", /* " with "? */
+          " de ",     " d'",       " du ",
+          "-in-",     "-at-",      0
+        }, /* list of first characters for all compounds[] entries */
+        compound_start[] = " -";
+
+    const char *const *cmpd;
+    char *p;
+
+    for (p = str; *p; ++p) {
+        /* substring starting at p can only match if *p is found
+           within compound_start[] */
+        if (!index(compound_start, *p))
+            continue;
+
+        /* check current substring against all words in the compound[] list */
+        for (cmpd = compounds; *cmpd; ++cmpd)
+            if (!strncmpi(p, *cmpd, (int) strlen(*cmpd)))
+                return p;
+    }
+    /* wasn't recognized as a compound phrase */
+    return 0;
+}
+
+/* Plural routine; once upon a time it may have been chiefly used for
+ * user-defined fruits, but it is now used extensively throughout the
+ * program.
+ *
+ * For fruit, we have to try to account for everything reasonable the
+ * player has; something unreasonable can still break the code.
+ * However, it's still a lot more accurate than "just add an 's' at the
+ * end", which Rogue uses...
+ *
+ * Also used for plural monster names ("Wiped out all homunculi." or the
+ * vanquished monsters list) and body parts.  A lot of unique monsters have
+ * names which get mangled by makeplural and/or makesingular.  They're not
+ * genocidable, and vanquished-mon handling does its own special casing
+ * (for uniques who've been revived and re-killed), so we don't bother
+ * trying to get those right here.
+ *
+ * Also misused by muse.c to convert 1st person present verbs to 2nd person.
+ * 3.6.0: made case-insensitive.
+ */
+char *
+makeplural(oldstr)
+const char *oldstr;
+{
+    register char *spot;
+    char lo_c, *str = nextobuf();
+    const char *excess = (char *) 0;
+    int len;
+
+    if (oldstr)
+        while (*oldstr == ' ')
+            oldstr++;
+    if (!oldstr || !*oldstr) {
+        impossible("plural of null?");
+        Strcpy(str, "s");
+        return str;
+    }
+    Strcpy(str, oldstr);
+
+    /*
+     * Skip changing "pair of" to "pairs of".  According to Webster, usual
+     * English usage is use pairs for humans, e.g. 3 pairs of dancers,
+     * and pair for objects and non-humans, e.g. 3 pair of boots.  We don't
+     * refer to pairs of humans in this game so just skip to the bottom.
+     */
+    if (!strncmpi(str, "pair of ", 8))
+        goto bottom;
+
+    /* look for "foo of bar" so that we can focus on "foo" */
+    if ((spot = singplur_compound(str)) != 0) {
+        excess = oldstr + (int) (spot - str);
+        *spot = '\0';
+    } else
+        spot = eos(str);
+
+    spot--;
+    while (spot > str && *spot == ' ')
+        spot--; /* Strip blanks from end */
+    *(spot + 1) = '\0';
+    /* Now spot is the last character of the string */
+
+    len = strlen(str);
+
+    /* Single letters */
+    if (len == 1 || !letter(*spot)) {
+        Strcpy(spot + 1, "'s");
+        goto bottom;
+    }
+
+    /* dispense with some words which don't need pluralization */
+    {
+        static const char *const already_plural[] = {
+            "ae",  /* algae, larvae, &c */
+            "matzot", 0,
+        };
+
+        /* spot+1: synch up with makesingular's usage */
+        if (singplur_lookup(str, spot + 1, TRUE, already_plural))
+            goto bottom;
+
+        /* more of same, but not suitable for blanket loop checking */
+        if ((len == 2 && !strcmpi(str, "ya"))
+            || (len >= 3 && !strcmpi(spot - 2, " ya")))
+            goto bottom;
+    }
+
+    /* man/men ("Wiped out all cavemen.") */
+    if (len >= 3 && !strcmpi(spot - 2, "man")
+        /* exclude shamans and humans etc */
+        && !badman(str, TRUE)) {
+        Strcasecpy(spot - 1, "en");
+        goto bottom;
+    }
+    if (lowc(*spot) == 'f') { /* (staff handled via one_off[]) */
+        lo_c = lowc(*(spot - 1));
+        if (len >= 3 && !strcmpi(spot - 2, "erf")) {
+            /* avoid "nerf" -> "nerves", "serf" -> "serves" */
+            ; /* fall through to default (append 's') */
+        } else if (index("lr", lo_c) || index(vowels, lo_c)) {
+            /* [aeioulr]f to [aeioulr]ves */
+            Strcasecpy(spot, "ves");
+            goto bottom;
+        }
+    }
+    /* ium/ia (mycelia, baluchitheria) */
+    if (len >= 3 && !strcmpi(spot - 2, "ium")) {
+        Strcasecpy(spot - 2, "ia");
+        goto bottom;
+    }
+    /* algae, larvae, hyphae (another fungus part) */
+    if ((len >= 4 && !strcmpi(spot - 3, "alga"))
+        || (len >= 5
+            && (!strcmpi(spot - 4, "hypha") || !strcmpi(spot - 4, "larva")))
+        || (len >= 6 && !strcmpi(spot - 5, "amoeba"))
+        || (len >= 8 && (!strcmpi(spot - 7, "vertebra")))) {
+        /* a to ae */
+        Strcasecpy(spot + 1, "e");
+        goto bottom;
+    }
+    /* fungus/fungi, homunculus/homunculi, but buses, lotuses, wumpuses */
+    if (len > 3 && !strcmpi(spot - 1, "us")
+        && !((len >= 5 && !strcmpi(spot - 4, "lotus"))
+             || (len >= 6 && !strcmpi(spot - 5, "wumpus")))) {
+        Strcasecpy(spot - 1, "i");
+        goto bottom;
+    }
+    /* sis/ses (nemesis) */
+    if (len >= 3 && !strcmpi(spot - 2, "sis")) {
+        Strcasecpy(spot - 1, "es");
+        goto bottom;
+    }
+    /* matzoh/matzot, possible food name */
+    if (len >= 6
+        && (!strcmpi(spot - 5, "matzoh") || !strcmpi(spot - 5, "matzah"))) {
+        Strcasecpy(spot - 1, "ot"); /* oh/ah -> ot */
+        goto bottom;
+    }
+    if (len >= 5
+        && (!strcmpi(spot - 4, "matzo") || !strcmpi(spot - 4, "matza"))) {
+        Strcasecpy(spot, "ot"); /* o/a -> ot */
+        goto bottom;
+    }
+
+    /* note: -eau/-eaux (gateau, bordeau...) */
+    /* note: ox/oxen, VAX/VAXen, goose/geese */
+
+    lo_c = lowc(*spot);
+
+    /* Ends in z, x, s, ch, sh; add an "es" */
+    if (index("zxs", lo_c)
+        || (len >= 2 && lo_c == 'h' && index("cs", lowc(*(spot - 1))))
+        /* Kludge to get "tomatoes" and "potatoes" right */
+        || (len >= 4 && !strcmpi(spot - 2, "ato"))
+        || (len >= 5 && !strcmpi(spot - 4, "dingo"))) {
+        Strcasecpy(spot + 1, "es"); /* append es */
+        goto bottom;
+    }
+    /* Ends in y preceded by consonant (note: also "qu") change to "ies" */
+    if (lo_c == 'y' && !index(vowels, lowc(*(spot - 1)))) {
+        Strcasecpy(spot, "ies"); /* y -> ies */
+        goto bottom;
+    }
+    /* Default: append an 's' */
+    Strcasecpy(spot + 1, "s");
+
+bottom:
+    if (excess)
+        Strcat(str, excess);
+    return str;
+}
+
+/*
+ * Singularize a string the user typed in; this helps reduce the complexity
+ * of readobjnam, and is also used in pager.c to singularize the string
+ * for which help is sought.
+ *
+ * "Manes" is ambiguous: monster type (keep s), or horse body part (drop s)?
+ * Its inclusion in as_is[]/special_subj[] makes it get treated as the former.
+ *
+ * A lot of unique monsters have names ending in s; plural, or singular
+ * from plural, doesn't make much sense for them so we don't bother trying.
+ * 3.6.0: made case-insensitive.
+ */
+char *
+makesingular(oldstr)
+const char *oldstr;
+{
+    register char *p, *bp;
+    const char *excess = 0;
+    char *str = nextobuf();
+
+    if (oldstr)
+        while (*oldstr == ' ')
+            oldstr++;
+    if (!oldstr || !*oldstr) {
+        impossible("singular of null?");
+        str[0] = '\0';
+        return str;
+    }
+
+    bp = strcpy(str, oldstr);
+
+    /* check for "foo of bar" so that we can focus on "foo" */
+    if ((p = singplur_compound(bp)) != 0) {
+        excess = oldstr + (int) (p - bp);
+        *p = '\0';
+    } else
+        p = eos(bp);
+
+    /* dispense with some words which don't need singularization */
+    if (singplur_lookup(bp, p, FALSE, special_subjs))
+        goto bottom;
+
+    /* remove -s or -es (boxes) or -ies (rubies) */
+    if (p >= bp + 1 && lowc(p[-1]) == 's') {
+        if (p >= bp + 2 && lowc(p[-2]) == 'e') {
+            if (p >= bp + 3 && lowc(p[-3]) == 'i') { /* "ies" */
+                if (!BSTRCMPI(bp, p - 7, "cookies")
+                    || !BSTRCMPI(bp, p - 4, "pies")
+                    || !BSTRCMPI(bp, p - 5, "mbies") /* zombie */
+                    || !BSTRCMPI(bp, p - 5, "yries")) /* valkyrie */
+                    goto mins;
+                Strcasecpy(p - 3, "y"); /* ies -> y */
+                goto bottom;
+            }
+            /* wolves, but f to ves isn't fully reversible */
+            if (p - 4 >= bp && (index("lr", lowc(*(p - 4)))
+                                || index(vowels, lowc(*(p - 4))))
+                && !BSTRCMPI(bp, p - 3, "ves")) {
+                if (!BSTRCMPI(bp, p - 6, "cloves")
+                    || !BSTRCMPI(bp, p - 6, "nerves"))
+                    goto mins;
+                Strcasecpy(p - 3, "f"); /* ves -> f */
+                goto bottom;
+            }
+            /* note: nurses, axes but boxes, wumpuses */
+            if (!BSTRCMPI(bp, p - 4, "eses")
+                || !BSTRCMPI(bp, p - 4, "oxes") /* boxes, foxes */
+                || !BSTRCMPI(bp, p - 4, "nxes") /* lynxes */
+                || !BSTRCMPI(bp, p - 4, "ches")
+                || !BSTRCMPI(bp, p - 4, "uses") /* lotuses */
+                || !BSTRCMPI(bp, p - 4, "sses") /* priestesses */
+                || !BSTRCMPI(bp, p - 5, "atoes") /* tomatoes */
+                || !BSTRCMPI(bp, p - 7, "dingoes")
+                || !BSTRCMPI(bp, p - 7, "Aleaxes")) {
+                *(p - 2) = '\0'; /* drop es */
+                goto bottom;
+            } /* else fall through to mins */
+
+            /* ends in 's' but not 'es' */
+        } else if (!BSTRCMPI(bp, p - 2, "us")) { /* lotus, fungus... */
+            if (BSTRCMPI(bp, p - 6, "tengus") /* but not these... */
+                && BSTRCMPI(bp, p - 7, "hezrous"))
+                goto bottom;
+        } else if (!BSTRCMPI(bp, p - 2, "ss")
+                   || !BSTRCMPI(bp, p - 5, " lens")
+                   || (p - 4 == bp && !strcmpi(p - 4, "lens"))) {
+            goto bottom;
+        }
+    mins:
+        *(p - 1) = '\0'; /* drop s */
+
+    } else { /* input doesn't end in 's' */
+
+        if (!BSTRCMPI(bp, p - 3, "men")
+            && !badman(bp, FALSE)) {
+            Strcasecpy(p - 2, "an");
+            goto bottom;
+        }
+        /* matzot -> matzo, algae -> alga */
+        if (!BSTRCMPI(bp, p - 6, "matzot") || !BSTRCMPI(bp, p - 2, "ae")) {
+            *(p - 1) = '\0'; /* drop t/e */
+            goto bottom;
+        }
+        /* balactheria -> balactherium */
+        if (p - 4 >= bp && !strcmpi(p - 2, "ia")
+            && index("lr", lowc(*(p - 3))) && lowc(*(p - 4)) == 'e') {
+            Strcasecpy(p - 1, "um"); /* a -> um */
+        }
+
+        /* here we cannot find the plural suffix */
+    }
+
+bottom:
+    /* if we stripped off a suffix (" of bar" from "foo of bar"),
+       put it back now [strcat() isn't actually 100% safe here...] */
+    if (excess)
+        Strcat(bp, excess);
+
+    return bp;
+}
+
+boolean
+badman(basestr, to_plural)
+const char *basestr;
+boolean to_plural;            /* true => makeplural, false => makesingular */
+{
+    int i, al;
+    char *endstr, *spot;
+    /* these are all the prefixes for *man that don't have a *men plural */
+    const char *no_men[] = {
+        "albu", "antihu", "anti", "ata", "auto", "bildungsro", "cai",
+        "cay", "ceru", "corner", "decu", "des", "dura", "fir",
+        "glass", "hanu", "het", "infrahu", "inhu", "land",
+        "meat", "nonhu", "otto", "out", "prehu", "protohu",
+        "subhu", "superhu", "talis", "unhu", "sha",
+        "hu", "un", "le", "re", "so", "to", "at", "a",
+    };
+    /* these are all the prefixes for *men that don't have a *man singular */
+    const char *no_man[] = {
+        "abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava",
+        "hegu", "preno", "sonar", "dai", "exa", "fla", "sta", "teg", "tegu",
+        "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya",
+        "o", "a",
+    };
+
+    if (!basestr || strlen(basestr) < 4)
+        return FALSE;
+
+    endstr = eos((char *)basestr);
+
+    if (to_plural) {
+        for (i = 0; i < SIZE(no_men); i++) {
+            al = (int) strlen(no_men[i]);
+            spot = endstr - (al + 3);
+            if (!BSTRNCMPI(basestr, spot, no_men[i], al)
+                && (spot == basestr || *(spot - 1) == ' '))
+                return TRUE;
+        }
+    } else {
+        for (i = 0; i < SIZE(no_man); i++) {
+            al = (int) strlen(no_man[i]);
+            spot = endstr - (al + 3);
+            if (!BSTRNCMPI(basestr, spot, no_man[i], al)
+                && (spot == basestr || *(spot - 1) == ' '))
+                return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+/* compare user string against object name string using fuzzy matching */
+STATIC_OVL boolean
+wishymatch(u_str, o_str, retry_inverted)
+const char *u_str;      /* from user, so might be variant spelling */
+const char *o_str;      /* from objects[], so is in canonical form */
+boolean retry_inverted; /* optional extra "of" handling */
+{
+    static NEARDATA const char detect_SP[] = "detect ",
+                               SP_detection[] = " detection";
+    char *p, buf[BUFSZ];
+
+    /* ignore spaces & hyphens and upper/lower case when comparing */
+    if (fuzzymatch(u_str, o_str, " -", TRUE))
+        return TRUE;
+
+    if (retry_inverted) {
+        const char *u_of, *o_of;
+
+        /* when just one of the strings is in the form "foo of bar",
+           convert it into "bar foo" and perform another comparison */
+        u_of = strstri(u_str, " of ");
+        o_of = strstri(o_str, " of ");
+        if (u_of && !o_of) {
+            Strcpy(buf, u_of + 4);
+            p = eos(strcat(buf, " "));
+            while (u_str < u_of)
+                *p++ = *u_str++;
+            *p = '\0';
+            return fuzzymatch(buf, o_str, " -", TRUE);
+        } else if (o_of && !u_of) {
+            Strcpy(buf, o_of + 4);
+            p = eos(strcat(buf, " "));
+            while (o_str < o_of)
+                *p++ = *o_str++;
+            *p = '\0';
+            return fuzzymatch(u_str, buf, " -", TRUE);
+        }
+    }
+
+    /* [note: if something like "elven speed boots" ever gets added, these
+       special cases should be changed to call wishymatch() recursively in
+       order to get the "of" inversion handling] */
+    if (!strncmp(o_str, "dwarvish ", 9)) {
+        if (!strncmpi(u_str, "dwarven ", 8))
+            return fuzzymatch(u_str + 8, o_str + 9, " -", TRUE);
+    } else if (!strncmp(o_str, "elven ", 6)) {
+        if (!strncmpi(u_str, "elvish ", 7))
+            return fuzzymatch(u_str + 7, o_str + 6, " -", TRUE);
+        else if (!strncmpi(u_str, "elfin ", 6))
+            return fuzzymatch(u_str + 6, o_str + 6, " -", TRUE);
+    } else if (!strncmp(o_str, detect_SP, sizeof detect_SP - 1)) {
+        /* check for "detect <foo>" vs "<foo> detection" */
+        if ((p = strstri(u_str, SP_detection)) != 0
+            && !*(p + sizeof SP_detection - 1)) {
+            /* convert "<foo> detection" into "detect <foo>" */
+            *p = '\0';
+            Strcat(strcpy(buf, detect_SP), u_str);
+            /* "detect monster" -> "detect monsters" */
+            if (!strcmpi(u_str, "monster"))
+                Strcat(buf, "s");
+            *p = ' ';
+            return fuzzymatch(buf, o_str, " -", TRUE);
+        }
+    } else if (strstri(o_str, SP_detection)) {
+        /* and the inverse, "<foo> detection" vs "detect <foo>" */
+        if (!strncmpi(u_str, detect_SP, sizeof detect_SP - 1)) {
+            /* convert "detect <foo>s" into "<foo> detection" */
+            p = makesingular(u_str + sizeof detect_SP - 1);
+            Strcat(strcpy(buf, p), SP_detection);
+            /* caller may be looping through objects[], so avoid
+               churning through all the obufs */
+            releaseobuf(p);
+            return fuzzymatch(buf, o_str, " -", TRUE);
+        }
+    } else if (strstri(o_str, "ability")) {
+        /* when presented with "foo of bar", makesingular() used to
+           singularize both foo & bar, but now only does so for foo */
+        /* catch "{potion(s),ring} of {gain,restore,sustain} abilities" */
+        if ((p = strstri(u_str, "abilities")) != 0
+            && !*(p + sizeof "abilities" - 1)) {
+            (void) strncpy(buf, u_str, (unsigned) (p - u_str));
+            Strcpy(buf + (p - u_str), "ability");
+            return fuzzymatch(buf, o_str, " -", TRUE);
+        }
+    } else if (!strcmp(o_str, "aluminum")) {
+        /* this special case doesn't really fit anywhere else... */
+        /* (note that " wand" will have been stripped off by now) */
+        if (!strcmpi(u_str, "aluminium"))
+            return fuzzymatch(u_str + 9, o_str + 8, " -", TRUE);
+    }
+
+    return FALSE;
+}
+
+struct o_range {
+    const char *name, oclass;
+    int f_o_range, l_o_range;
+};
+
+/* wishable subranges of objects */
+STATIC_OVL NEARDATA const struct o_range o_ranges[] = {
+    { "bag", TOOL_CLASS, SACK, BAG_OF_TRICKS },
+    { "lamp", TOOL_CLASS, OIL_LAMP, MAGIC_LAMP },
+    { "candle", TOOL_CLASS, TALLOW_CANDLE, WAX_CANDLE },
+    { "horn", TOOL_CLASS, TOOLED_HORN, HORN_OF_PLENTY },
+    { "shield", ARMOR_CLASS, SMALL_SHIELD, SHIELD_OF_REFLECTION },
+    { "hat", ARMOR_CLASS, FEDORA, DUNCE_CAP },
+    { "helm", ARMOR_CLASS, ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY },
+    { "gloves", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY },
+    { "gauntlets", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY },
+    { "boots", ARMOR_CLASS, LOW_BOOTS, LEVITATION_BOOTS },
+    { "shoes", ARMOR_CLASS, LOW_BOOTS, IRON_SHOES },
+    { "cloak", ARMOR_CLASS, MUMMY_WRAPPING, CLOAK_OF_DISPLACEMENT },
+    { "shirt", ARMOR_CLASS, HAWAIIAN_SHIRT, T_SHIRT },
+    { "dragon scales", ARMOR_CLASS, GRAY_DRAGON_SCALES,
+      YELLOW_DRAGON_SCALES },
+    { "dragon scale mail", ARMOR_CLASS, GRAY_DRAGON_SCALE_MAIL,
+      YELLOW_DRAGON_SCALE_MAIL },
+    { "sword", WEAPON_CLASS, SHORT_SWORD, KATANA },
+    { "venom", VENOM_CLASS, BLINDING_VENOM, ACID_VENOM },
+    { "gray stone", GEM_CLASS, LUCKSTONE, FLINT },
+    { "grey stone", GEM_CLASS, LUCKSTONE, FLINT },
+};
+
+/* alternate spellings; if the difference is only the presence or
+   absence of spaces and/or hyphens (such as "pickaxe" vs "pick axe"
+   vs "pick-axe") then there is no need for inclusion in this list;
+   likewise for ``"of" inversions'' ("boots of speed" vs "speed boots") */
+struct alt_spellings {
+    const char *sp;
+    int ob;
+} spellings[] = {
+    { "pickax", PICK_AXE },
+    { "whip", BULLWHIP },
+    { "saber", SILVER_SABER },
+    { "silver sabre", SILVER_SABER },
+    { "smooth shield", SHIELD_OF_REFLECTION },
+    { "grey dragon scale mail", GRAY_DRAGON_SCALE_MAIL },
+    { "grey dragon scales", GRAY_DRAGON_SCALES },
+    { "iron ball", HEAVY_IRON_BALL },
+    { "lantern", BRASS_LANTERN },
+    { "mattock", DWARVISH_MATTOCK },
+    { "amulet of poison resistance", AMULET_VERSUS_POISON },
+    { "potion of sleep", POT_SLEEPING },
+    { "stone", ROCK },
+    { "camera", EXPENSIVE_CAMERA },
+    { "tee shirt", T_SHIRT },
+    { "can", TIN },
+    { "can opener", TIN_OPENER },
+    { "kelp", KELP_FROND },
+    { "eucalyptus", EUCALYPTUS_LEAF },
+    { "royal jelly", LUMP_OF_ROYAL_JELLY },
+    { "lembas", LEMBAS_WAFER },
+    { "marker", MAGIC_MARKER },
+    { "hook", GRAPPLING_HOOK },
+    { "grappling iron", GRAPPLING_HOOK },
+    { "grapnel", GRAPPLING_HOOK },
+    { "grapple", GRAPPLING_HOOK },
+    { "protection from shape shifters", RIN_PROTECTION_FROM_SHAPE_CHAN },
+    /* normally we wouldn't have to worry about unnecessary <space>, but
+       " stone" will get stripped off, preventing a wishymatch; that actually
+       lets "flint stone" be a match, so we also accept bogus "flintstone" */
+    { "luck stone", LUCKSTONE },
+    { "load stone", LOADSTONE },
+    { "touch stone", TOUCHSTONE },
+    { "flintstone", FLINT },
+    { (const char *) 0, 0 },
+};
+
+STATIC_OVL short
+rnd_otyp_by_wpnskill(skill)
+schar skill;
+{
+    int i, n = 0;
+    short otyp = STRANGE_OBJECT;
+    for (i = bases[WEAPON_CLASS];
+         i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++)
+        if (objects[i].oc_skill == skill) {
+            n++;
+            otyp = i;
+        }
+    if (n > 0) {
+        n = rn2(n);
+        for (i = bases[WEAPON_CLASS];
+             i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++)
+            if (objects[i].oc_skill == skill)
+                if (--n < 0)
+                    return i;
+    }
+    return otyp;
+}
+
+STATIC_OVL short
+rnd_otyp_by_namedesc(name, oclass)
+char *name;
+char oclass;
+{
+    int i, n = 0;
+    short validobjs[NUM_OBJECTS];
+    register const char *zn;
+    long maxprob = 0;
+
+    if (!name)
+        return STRANGE_OBJECT;
+
+    memset((genericptr_t) validobjs, 0, sizeof(validobjs));
+
+    for (i = oclass ? bases[(int)oclass] : STRANGE_OBJECT + 1;
+         i < NUM_OBJECTS && (!oclass || objects[i].oc_class == oclass);
+         ++i) {
+        /* don't match extra descriptions (w/o real name) */
+        if ((zn = OBJ_NAME(objects[i])) == 0)
+            continue;
+        if (wishymatch(name, zn, TRUE)
+            || ((zn = OBJ_DESCR(objects[i])) != 0
+                && wishymatch(name, zn, FALSE))
+            || ((zn = objects[i].oc_uname) != 0
+                && wishymatch(name, zn, FALSE))) {
+            validobjs[n++] = (short) i;
+            maxprob += (objects[i].oc_prob + 1);
+        }
+    }
+
+    if (n > 0 && maxprob) {
+        long prob = rn2(maxprob);
+
+        i = 0;
+        while (i < n - 1
+               && (prob -= (objects[validobjs[i]].oc_prob + 1)) >= 0)
+            i++;
+        return validobjs[i];
+    }
+    return STRANGE_OBJECT;
+}
+
+/*
+ * Return something wished for.  Specifying a null pointer for
+ * the user request string results in a random object.  Otherwise,
+ * if asking explicitly for "nothing" (or "nil") return no_wish;
+ * if not an object return &zeroobj; if an error (no matching object),
+ * return null.
+ */
+struct obj *
+readobjnam(bp, no_wish)
+register char *bp;
+struct obj *no_wish;
+{
+    register char *p;
+    register int i;
+    register struct obj *otmp;
+    int cnt, spe, spesgn, typ, very, rechrg;
+    int blessed, uncursed, iscursed, ispoisoned, isgreased;
+    int eroded, eroded2, erodeproof;
+    int halfeaten, mntmp, contents;
+    int islit, unlabeled, ishistoric, isdiluted, trapped;
+    int tmp, tinv, tvariety;
+    int wetness, gsize = 0;
+    struct fruit *f;
+    int ftype = context.current_fruit;
+    char fruitbuf[BUFSZ];
+    /* Fruits may not mess up the ability to wish for real objects (since
+     * you can leave a fruit in a bones file and it will be added to
+     * another person's game), so they must be checked for last, after
+     * stripping all the possible prefixes and seeing if there's a real
+     * name in there.  So we have to save the full original name.  However,
+     * it's still possible to do things like "uncursed burnt Alaska",
+     * or worse yet, "2 burned 5 course meals", so we need to loop to
+     * strip off the prefixes again, this time stripping only the ones
+     * possible on food.
+     * We could get even more detailed so as to allow food names with
+     * prefixes that _are_ possible on food, so you could wish for
+     * "2 3 alarm chilis".  Currently this isn't allowed; options.c
+     * automatically sticks 'candied' in front of such names.
+     */
+    char oclass;
+    char *un, *dn, *actualn, *origbp = bp;
+    const char *name = 0;
+
+    cnt = spe = spesgn = typ = very = rechrg = blessed = uncursed = iscursed =
+        ispoisoned = isgreased = eroded = eroded2 = erodeproof = halfeaten =
+            islit = unlabeled = ishistoric = isdiluted = trapped = 0;
+    tvariety = RANDOM_TIN;
+    mntmp = NON_PM;
+#define UNDEFINED 0
+#define EMPTY 1
+#define SPINACH 2
+    contents = UNDEFINED;
+    oclass = 0;
+    actualn = dn = un = 0;
+    wetness = 0;
+
+    if (!bp)
+        goto any;
+    /* first, remove extra whitespace they may have typed */
+    (void) mungspaces(bp);
+    /* allow wishing for "nothing" to preserve wishless conduct...
+       [now requires "wand of nothing" if that's what was really wanted] */
+    if (!strcmpi(bp, "nothing") || !strcmpi(bp, "nil")
+        || !strcmpi(bp, "none"))
+        return no_wish;
+    /* save the [nearly] unmodified choice string */
+    Strcpy(fruitbuf, bp);
+
+    for (;;) {
+        register int l;
+
+        if (!bp || !*bp)
+            goto any;
+        if (!strncmpi(bp, "an ", l = 3) || !strncmpi(bp, "a ", l = 2)) {
+            cnt = 1;
+        } else if (!strncmpi(bp, "the ", l = 4)) {
+            ; /* just increment `bp' by `l' below */
+        } else if (!cnt && digit(*bp) && strcmp(bp, "0")) {
+            cnt = atoi(bp);
+            while (digit(*bp))
+                bp++;
+            while (*bp == ' ')
+                bp++;
+            l = 0;
+        } else if (*bp == '+' || *bp == '-') {
+            spesgn = (*bp++ == '+') ? 1 : -1;
+            spe = atoi(bp);
+            while (digit(*bp))
+                bp++;
+            while (*bp == ' ')
+                bp++;
+            l = 0;
+        } else if (!strncmpi(bp, "blessed ", l = 8)
+                   || !strncmpi(bp, "holy ", l = 5)) {
+            blessed = 1;
+        } else if (!strncmpi(bp, "moist ", l = 6)
+                   || !strncmpi(bp, "wet ", l = 4)) {
+            if (!strncmpi(bp, "wet ", 4))
+                wetness = rn2(3) + 3;
+            else
+                wetness = rnd(2);
+        } else if (!strncmpi(bp, "cursed ", l = 7)
+                   || !strncmpi(bp, "unholy ", l = 7)) {
+            iscursed = 1;
+        } else if (!strncmpi(bp, "uncursed ", l = 9)) {
+            uncursed = 1;
+        } else if (!strncmpi(bp, "rustproof ", l = 10)
+                   || !strncmpi(bp, "erodeproof ", l = 11)
+                   || !strncmpi(bp, "corrodeproof ", l = 13)
+                   || !strncmpi(bp, "fixed ", l = 6)
+                   || !strncmpi(bp, "fireproof ", l = 10)
+                   || !strncmpi(bp, "rotproof ", l = 9)) {
+            erodeproof = 1;
+        } else if (!strncmpi(bp, "lit ", l = 4)
+                   || !strncmpi(bp, "burning ", l = 8)) {
+            islit = 1;
+        } else if (!strncmpi(bp, "unlit ", l = 6)
+                   || !strncmpi(bp, "extinguished ", l = 13)) {
+            islit = 0;
+            /* "unlabeled" and "blank" are synonymous */
+        } else if (!strncmpi(bp, "unlabeled ", l = 10)
+                   || !strncmpi(bp, "unlabelled ", l = 11)
+                   || !strncmpi(bp, "blank ", l = 6)) {
+            unlabeled = 1;
+        } else if (!strncmpi(bp, "poisoned ", l = 9)) {
+            ispoisoned = 1;
+            /* "trapped" recognized but not honored outside wizard mode */
+        } else if (!strncmpi(bp, "trapped ", l = 8)) {
+            trapped = 0; /* undo any previous "untrapped" */
+            if (wizard)
+                trapped = 1;
+        } else if (!strncmpi(bp, "untrapped ", l = 10)) {
+            trapped = 2; /* not trapped */
+        } else if (!strncmpi(bp, "greased ", l = 8)) {
+            isgreased = 1;
+        } else if (!strncmpi(bp, "very ", l = 5)) {
+            /* very rusted very heavy iron ball */
+            very = 1;
+        } else if (!strncmpi(bp, "thoroughly ", l = 11)) {
+            very = 2;
+        } else if (!strncmpi(bp, "rusty ", l = 6)
+                   || !strncmpi(bp, "rusted ", l = 7)
+                   || !strncmpi(bp, "burnt ", l = 6)
+                   || !strncmpi(bp, "burned ", l = 7)) {
+            eroded = 1 + very;
+            very = 0;
+        } else if (!strncmpi(bp, "corroded ", l = 9)
+                   || !strncmpi(bp, "rotted ", l = 7)) {
+            eroded2 = 1 + very;
+            very = 0;
+        } else if (!strncmpi(bp, "partly eaten ", l = 13)
+                   || !strncmpi(bp, "partially eaten ", l = 16)) {
+            halfeaten = 1;
+        } else if (!strncmpi(bp, "historic ", l = 9)) {
+            ishistoric = 1;
+        } else if (!strncmpi(bp, "diluted ", l = 8)) {
+            isdiluted = 1;
+        } else if (!strncmpi(bp, "empty ", l = 6)) {
+            contents = EMPTY;
+        } else if (!strncmpi(bp, "small ", l = 6)) { /* glob sizes */
+            gsize = 1;
+        } else if (!strncmpi(bp, "medium ", l = 7)) {
+            /* xname() doesn't display "medium" but without this
+               there'd be no way to ask for the intermediate size */
+            gsize = 2;
+        } else if (!strncmpi(bp, "large ", l = 6)) {
+            /* "very large " had "very " peeled off on previous iteration */
+            gsize = (very != 1) ? 3 : 4;
+        } else
+            break;
+        bp += l;
+    }
+    if (!cnt)
+        cnt = 1; /* %% what with "gems" etc. ? */
+    if (strlen(bp) > 1 && (p = rindex(bp, '(')) != 0) {
+        boolean keeptrailingchars = TRUE;
+
+        p[(p > bp && p[-1] == ' ') ? -1 : 0] = '\0'; /*terminate bp */
+        ++p; /* advance past '(' */
+        if (!strncmpi(p, "lit)", 4)) {
+            islit = 1;
+            p += 4 - 1; /* point at ')' */
+        } else {
+            spe = atoi(p);
+            while (digit(*p))
+                p++;
+            if (*p == ':') {
+                p++;
+                rechrg = spe;
+                spe = atoi(p);
+                while (digit(*p))
+                    p++;
+            }
+            if (*p != ')') {
+                spe = rechrg = 0;
+                /* mis-matched parentheses; rest of string will be ignored
+                 * [probably we should restore everything back to '('
+                 * instead since it might be part of "named ..."]
+                 */
+                keeptrailingchars = FALSE;
+            } else {
+                spesgn = 1;
+            }
+        }
+        if (keeptrailingchars) {
+            char *pp = eos(bp);
+
+            /* 'pp' points at 'pb's terminating '\0',
+               'p' points at ')' and will be incremented past it */
+            do {
+                *pp++ = *++p;
+            } while (*p);
+        }
+    }
+    /*
+     * otmp->spe is type schar, so we don't want spe to be any bigger or
+     * smaller.  Also, spe should always be positive --some cheaters may
+     * try to confuse atoi().
+     */
+    if (spe < 0) {
+        spesgn = -1; /* cheaters get what they deserve */
+        spe = abs(spe);
+    }
+    if (spe > SCHAR_LIM)
+        spe = SCHAR_LIM;
+    if (rechrg < 0 || rechrg > 7)
+        rechrg = 7; /* recharge_limit */
+
+    /* now we have the actual name, as delivered by xname, say
+     *  green potions called whisky
+     *  scrolls labeled "QWERTY"
+     *  egg
+     *  fortune cookies
+     *  very heavy iron ball named hoei
+     *  wand of wishing
+     *  elven cloak
+     */
+    if ((p = strstri(bp, " named ")) != 0) {
+        *p = 0;
+        name = p + 7;
+    }
+    if ((p = strstri(bp, " called ")) != 0) {
+        *p = 0;
+        un = p + 8;
+        /* "helmet called telepathy" is not "helmet" (a specific type)
+         * "shield called reflection" is not "shield" (a general type)
+         */
+        for (i = 0; i < SIZE(o_ranges); i++)
+            if (!strcmpi(bp, o_ranges[i].name)) {
+                oclass = o_ranges[i].oclass;
+                goto srch;
+            }
+    }
+    if ((p = strstri(bp, " labeled ")) != 0) {
+        *p = 0;
+        dn = p + 9;
+    } else if ((p = strstri(bp, " labelled ")) != 0) {
+        *p = 0;
+        dn = p + 10;
+    }
+    if ((p = strstri(bp, " of spinach")) != 0) {
+        *p = 0;
+        contents = SPINACH;
+    }
+
+    /*
+     * Skip over "pair of ", "pairs of", "set of" and "sets of".
+     *
+     * Accept "3 pair of boots" as well as "3 pairs of boots".  It is
+     * valid English either way.  See makeplural() for more on pair/pairs.
+     *
+     * We should only double count if the object in question is not
+     * referred to as a "pair of".  E.g. We should double if the player
+     * types "pair of spears", but not if the player types "pair of
+     * lenses".  Luckily (?) all objects that are referred to as pairs
+     * -- boots, gloves, and lenses -- are also not mergable, so cnt is
+     * ignored anyway.
+     */
+    if (!strncmpi(bp, "pair of ", 8)) {
+        bp += 8;
+        cnt *= 2;
+    } else if (!strncmpi(bp, "pairs of ", 9)) {
+        bp += 9;
+        if (cnt > 1)
+            cnt *= 2;
+    } else if (!strncmpi(bp, "set of ", 7)) {
+        bp += 7;
+    } else if (!strncmpi(bp, "sets of ", 8)) {
+        bp += 8;
+    }
+
+    /* intercept pudding globs here; they're a valid wish target,
+     * but we need them to not get treated like a corpse.
+     *
+     * also don't let player wish for multiple globs.
+     */
+    if ((p = strstri(bp, "glob of ")) != 0
+        || (p = strstri(bp, "globs of ")) != 0) {
+        int globoffset = (*(p + 4) == 's') ? 9 : 8;
+
+        if ((mntmp = name_to_mon(p + globoffset)) >= PM_GRAY_OOZE
+            && mntmp <= PM_BLACK_PUDDING) {
+            mntmp = NON_PM; /* lie to ourselves */
+            cnt = 0;        /* force only one */
+        }
+    } else {
+        /*
+         * Find corpse type using "of" (figurine of an orc, tin of orc meat)
+         * Don't check if it's a wand or spellbook.
+         * (avoid "wand/finger of death" confusion).
+         */
+        if (!strstri(bp, "wand ") && !strstri(bp, "spellbook ")
+            && !strstri(bp, "finger ")) {
+            if ((p = strstri(bp, "tin of ")) != 0) {
+                if (!strcmpi(p + 7, "spinach")) {
+                    contents = SPINACH;
+                    mntmp = NON_PM;
+                } else {
+                    tmp = tin_variety_txt(p + 7, &tinv);
+                    tvariety = tinv;
+                    mntmp = name_to_mon(p + 7 + tmp);
+                }
+                typ = TIN;
+                goto typfnd;
+            } else if ((p = strstri(bp, " of ")) != 0
+                       && (mntmp = name_to_mon(p + 4)) >= LOW_PM)
+                *p = 0;
+        }
+    }
+    /* Find corpse type w/o "of" (red dragon scale mail, yeti corpse) */
+    if (strncmpi(bp, "samurai sword", 13)  /* not the "samurai" monster! */
+        && strncmpi(bp, "wizard lock", 11) /* not the "wizard" monster! */
+        && strncmpi(bp, "ninja-to", 8)     /* not the "ninja" rank */
+        && strncmpi(bp, "master key", 10)  /* not the "master" rank */
+        && strncmpi(bp, "magenta", 7)) {   /* not the "mage" rank */
+        if (mntmp < LOW_PM && strlen(bp) > 2
+            && (mntmp = name_to_mon(bp)) >= LOW_PM) {
+            int mntmptoo, mntmplen; /* double check for rank title */
+            char *obp = bp;
+
+            mntmptoo = title_to_mon(bp, (int *) 0, &mntmplen);
+            bp += (mntmp != mntmptoo) ? (int) strlen(mons[mntmp].mname)
+                                      : mntmplen;
+            if (*bp == ' ') {
+                bp++;
+            } else if (!strncmpi(bp, "s ", 2)) {
+                bp += 2;
+            } else if (!strncmpi(bp, "es ", 3)) {
+                bp += 3;
+            } else if (!*bp && !actualn && !dn && !un && !oclass) {
+                /* no referent; they don't really mean a monster type */
+                bp = obp;
+                mntmp = NON_PM;
+            }
+        }
+    }
+
+    /* first change to singular if necessary */
+    if (*bp) {
+        char *sng = makesingular(bp);
+        if (strcmp(bp, sng)) {
+            if (cnt == 1)
+                cnt = 2;
+            Strcpy(bp, sng);
+        }
+    }
+
+    /* Alternate spellings (pick-ax, silver sabre, &c) */
+    {
+        struct alt_spellings *as = spellings;
+
+        while (as->sp) {
+            if (fuzzymatch(bp, as->sp, " -", TRUE)) {
+                typ = as->ob;
+                goto typfnd;
+            }
+            as++;
+        }
+        /* can't use spellings list for this one due to shuffling */
+        if (!strncmpi(bp, "grey spell", 10))
+            *(bp + 2) = 'a';
+
+        if ((p = strstri(bp, "armour")) != 0) {
+            /* skip past "armo", then copy remainder beyond "u" */
+            p += 4;
+            while ((*p = *(p + 1)) != '\0')
+                ++p; /* self terminating */
+        }
+    }
+
+    /* dragon scales - assumes order of dragons */
+    if (!strcmpi(bp, "scales") && mntmp >= PM_GRAY_DRAGON
+        && mntmp <= PM_YELLOW_DRAGON) {
+        typ = GRAY_DRAGON_SCALES + mntmp - PM_GRAY_DRAGON;
+        mntmp = NON_PM; /* no monster */
+        goto typfnd;
+    }
+
+    p = eos(bp);
+    if (!BSTRCMPI(bp, p - 10, "holy water")) {
+        typ = POT_WATER;
+        if ((p - bp) >= 12 && *(p - 12) == 'u')
+            iscursed = 1; /* unholy water */
+        else
+            blessed = 1;
+        goto typfnd;
+    }
+    if (unlabeled && !BSTRCMPI(bp, p - 6, "scroll")) {
+        typ = SCR_BLANK_PAPER;
+        goto typfnd;
+    }
+    if (unlabeled && !BSTRCMPI(bp, p - 9, "spellbook")) {
+        typ = SPE_BLANK_PAPER;
+        goto typfnd;
+    }
+    /*
+     * NOTE: Gold pieces are handled as objects nowadays, and therefore
+     * this section should probably be reconsidered as well as the entire
+     * gold/money concept.  Maybe we want to add other monetary units as
+     * well in the future. (TH)
+     */
+    if (!BSTRCMPI(bp, p - 10, "gold piece")
+        || !BSTRCMPI(bp, p - 7, "zorkmid")
+        || !strcmpi(bp, "gold") || !strcmpi(bp, "money")
+        || !strcmpi(bp, "coin") || *bp == GOLD_SYM) {
+        if (cnt > 5000 && !wizard)
+            cnt = 5000;
+        else if (cnt < 1)
+            cnt = 1;
+        otmp = mksobj(GOLD_PIECE, FALSE, FALSE);
+        otmp->quan = (long) cnt;
+        otmp->owt = weight(otmp);
+        context.botl = 1;
+        return otmp;
+    }
+
+    /* check for single character object class code ("/" for wand, &c) */
+    if (strlen(bp) == 1 && (i = def_char_to_objclass(*bp)) < MAXOCLASSES
+        && i > ILLOBJ_CLASS && (i != VENOM_CLASS || wizard)) {
+        oclass = i;
+        goto any;
+    }
+
+    /* Search for class names: XXXXX potion, scroll of XXXXX.  Avoid */
+    /* false hits on, e.g., rings for "ring mail". */
+    if (strncmpi(bp, "enchant ", 8)
+        && strncmpi(bp, "destroy ", 8)
+        && strncmpi(bp, "detect food", 11)
+        && strncmpi(bp, "food detection", 14)
+        && strncmpi(bp, "ring mail", 9)
+        && strncmpi(bp, "studded leather armor", 21)
+        && strncmpi(bp, "leather armor", 13)
+        && strncmpi(bp, "tooled horn", 11)
+        && strncmpi(bp, "food ration", 11)
+        && strncmpi(bp, "meat ring", 9))
+        for (i = 0; i < (int) (sizeof wrpsym); i++) {
+            register int j = strlen(wrp[i]);
+
+            if (!strncmpi(bp, wrp[i], j)) {
+                oclass = wrpsym[i];
+                if (oclass != AMULET_CLASS) {
+                    bp += j;
+                    if (!strncmpi(bp, " of ", 4))
+                        actualn = bp + 4;
+                    /* else if(*bp) ?? */
+                } else
+                    actualn = bp;
+                goto srch;
+            }
+            if (!BSTRCMPI(bp, p - j, wrp[i])) {
+                oclass = wrpsym[i];
+                p -= j;
+                *p = 0;
+                if (p > bp && p[-1] == ' ')
+                    p[-1] = 0;
+                actualn = dn = bp;
+                goto srch;
+            }
+        }
+
+    /* Wishing in wizard mode can create traps and furniture.
+     * Part I:  distinguish between trap and object for the two
+     * types of traps which have corresponding objects:  bear trap
+     * and land mine.  "beartrap" (object) and "bear trap" (trap)
+     * have a difference in spelling which we used to exploit by
+     * adding a special case in wishymatch(), but "land mine" is
+     * spelled the same either way so needs different handing.
+     * Since we need something else for land mine, we've dropped
+     * the bear trap hack so that both are handled exactly the
+     * same.  To get an armed trap instead of a disarmed object,
+     * the player can prefix either the object name or the trap
+     * name with "trapped " (which ordinarily applies to chests
+     * and tins), or append something--anything at all except for
+     * " object", but " trap" is suggested--to either the trap
+     * name or the object name.
+     */
+    if (wizard && (!strncmpi(bp, "bear", 4) || !strncmpi(bp, "land", 4))) {
+        boolean beartrap = (lowc(*bp) == 'b');
+        char *zp = bp + 4; /* skip "bear"/"land" */
+
+        if (*zp == ' ')
+            ++zp; /* embedded space is optional */
+        if (!strncmpi(zp, beartrap ? "trap" : "mine", 4)) {
+            zp += 4;
+            if (trapped == 2 || !strcmpi(zp, " object")) {
+                /* "untrapped <foo>" or "<foo> object" */
+                typ = beartrap ? BEARTRAP : LAND_MINE;
+                goto typfnd;
+            } else if (trapped == 1 || *zp != '\0') {
+                /* "trapped <foo>" or "<foo> trap" (actually "<foo>*") */
+                int idx = trap_to_defsym(beartrap ? BEAR_TRAP : LANDMINE);
+
+                /* use canonical trap spelling, skip object matching */
+                Strcpy(bp, defsyms[idx].explanation);
+                goto wiztrap;
+            }
+            /* [no prefix or suffix; we're going to end up matching
+               the object name and getting a disarmed trap object] */
+        }
+    }
+
+retry:
+    /* "grey stone" check must be before general "stone" */
+    for (i = 0; i < SIZE(o_ranges); i++)
+        if (!strcmpi(bp, o_ranges[i].name)) {
+            typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range);
+            goto typfnd;
+        }
+
+    if (!BSTRCMPI(bp, p - 6, " stone") || !BSTRCMPI(bp, p - 4, " gem")) {
+        p[!strcmpi(p - 4, " gem") ? -4 : -6] = '\0';
+        oclass = GEM_CLASS;
+        dn = actualn = bp;
+        goto srch;
+    } else if (!strcmpi(bp, "looking glass")) {
+        ; /* avoid false hit on "* glass" */
+    } else if (!BSTRCMPI(bp, p - 6, " glass") || !strcmpi(bp, "glass")) {
+        register char *g = bp;
+        if (strstri(g, "broken"))
+            return (struct obj *) 0;
+        if (!strncmpi(g, "worthless ", 10))
+            g += 10;
+        if (!strncmpi(g, "piece of ", 9))
+            g += 9;
+        if (!strncmpi(g, "colored ", 8))
+            g += 8;
+        else if (!strncmpi(g, "coloured ", 9))
+            g += 9;
+        if (!strcmpi(g, "glass")) { /* choose random color */
+            /* 9 different kinds */
+            typ = LAST_GEM + rnd(9);
+            if (objects[typ].oc_class == GEM_CLASS)
+                goto typfnd;
+            else
+                typ = 0; /* somebody changed objects[]? punt */
+        } else { /* try to construct canonical form */
+            char tbuf[BUFSZ];
+
+            Strcpy(tbuf, "worthless piece of ");
+            Strcat(tbuf, g); /* assume it starts with the color */
+            Strcpy(bp, tbuf);
+        }
+    }
+
+    actualn = bp;
+    if (!dn)
+        dn = actualn; /* ex. "skull cap" */
+srch:
+    /* check real names of gems first */
+    if (!oclass && actualn) {
+        for (i = bases[GEM_CLASS]; i <= LAST_GEM; i++) {
+            register const char *zn;
+
+            if ((zn = OBJ_NAME(objects[i])) != 0 && !strcmpi(actualn, zn)) {
+                typ = i;
+                goto typfnd;
+            }
+        }
+        /* "tin of foo" would be caught above, but plain "tin" has
+           a random chance of yielding "tin wand" unless we do this */
+        if (!strcmpi(actualn, "tin")) {
+            typ = TIN;
+            goto typfnd;
+        }
+    }
+
+    if (((typ = rnd_otyp_by_namedesc(actualn, oclass)) != STRANGE_OBJECT)
+        || ((typ = rnd_otyp_by_namedesc(dn, oclass)) != STRANGE_OBJECT)
+        || ((typ = rnd_otyp_by_namedesc(un, oclass)) != STRANGE_OBJECT)
+        || ((typ = rnd_otyp_by_namedesc(origbp, oclass)) != STRANGE_OBJECT))
+        goto typfnd;
+    typ = 0;
+
+    if (actualn) {
+        struct Jitem *j = Japanese_items;
+
+        while (j->item) {
+            if (actualn && !strcmpi(actualn, j->name)) {
+                typ = j->item;
+                goto typfnd;
+            }
+            j++;
+        }
+    }
+    /* if we've stripped off "armor" and failed to match anything
+       in objects[], append "mail" and try again to catch misnamed
+       requests like "plate armor" and "yellow dragon scale armor" */
+    if (oclass == ARMOR_CLASS && !strstri(bp, "mail")) {
+        /* modifying bp's string is ok; we're about to resort
+           to random armor if this also fails to match anything */
+        Strcat(bp, " mail");
+        goto retry;
+    }
+    if (!strcmpi(bp, "spinach")) {
+        contents = SPINACH;
+        typ = TIN;
+        goto typfnd;
+    }
+    /* Note: not strcmpi.  2 fruits, one capital, one not, are possible.
+       Also not strncmp.  We used to ignore trailing text with it, but
+       that resulted in "grapefruit" matching "grape" if the latter came
+       earlier than the former in the fruit list. */
+    {
+        char *fp;
+        int l, cntf;
+        int blessedf, iscursedf, uncursedf, halfeatenf;
+
+        blessedf = iscursedf = uncursedf = halfeatenf = 0;
+        cntf = 0;
+
+        fp = fruitbuf;
+        for (;;) {
+            if (!fp || !*fp)
+                break;
+            if (!strncmpi(fp, "an ", l = 3) || !strncmpi(fp, "a ", l = 2)) {
+                cntf = 1;
+            } else if (!cntf && digit(*fp)) {
+                cntf = atoi(fp);
+                while (digit(*fp))
+                    fp++;
+                while (*fp == ' ')
+                    fp++;
+                l = 0;
+            } else if (!strncmpi(fp, "blessed ", l = 8)) {
+                blessedf = 1;
+            } else if (!strncmpi(fp, "cursed ", l = 7)) {
+                iscursedf = 1;
+            } else if (!strncmpi(fp, "uncursed ", l = 9)) {
+                uncursedf = 1;
+            } else if (!strncmpi(fp, "partly eaten ", l = 13)
+                       || !strncmpi(fp, "partially eaten ", l = 16)) {
+                halfeatenf = 1;
+            } else
+                break;
+            fp += l;
+        }
+
+        for (f = ffruit; f; f = f->nextf) {
+            /* match type: 0=none, 1=exact, 2=singular, 3=plural */
+            int ftyp = 0;
+
+            if (!strcmp(fp, f->fname))
+                ftyp = 1;
+            else if (!strcmp(fp, makesingular(f->fname)))
+                ftyp = 2;
+            else if (!strcmp(fp, makeplural(f->fname)))
+                ftyp = 3;
+            if (ftyp) {
+                typ = SLIME_MOLD;
+                blessed = blessedf;
+                iscursed = iscursedf;
+                uncursed = uncursedf;
+                halfeaten = halfeatenf;
+                /* adjust count if user explicitly asked for
+                   singular amount (can't happen unless fruit
+                   has been given an already pluralized name)
+                   or for plural amount */
+                if (ftyp == 2 && !cntf)
+                    cntf = 1;
+                else if (ftyp == 3 && !cntf)
+                    cntf = 2;
+                cnt = cntf;
+                ftype = f->fid;
+                goto typfnd;
+            }
+        }
+    }
+
+    if (!oclass && actualn) {
+        short objtyp;
+
+        /* Perhaps it's an artifact specified by name, not type */
+        name = artifact_name(actualn, &objtyp);
+        if (name) {
+            typ = objtyp;
+            goto typfnd;
+        }
+    }
+/* Let wizards wish for traps and furniture.
+ * Must come after objects check so wizards can still wish for
+ * trap objects like beartraps.
+ * Disallow such topology tweaks for WIZKIT startup wishes.
+ */
+wiztrap:
+    if (wizard && !program_state.wizkit_wishing) {
+        struct rm *lev;
+        int trap, x = u.ux, y = u.uy;
+
+        for (trap = NO_TRAP + 1; trap < TRAPNUM; trap++) {
+            struct trap *t;
+            const char *tname;
+
+            tname = defsyms[trap_to_defsym(trap)].explanation;
+            if (strncmpi(tname, bp, strlen(tname)))
+                continue;
+            /* found it; avoid stupid mistakes */
+            if ((trap == TRAPDOOR || trap == HOLE) && !Can_fall_thru(&u.uz))
+                trap = ROCKTRAP;
+            if ((t = maketrap(x, y, trap)) != 0) {
+                trap = t->ttyp;
+                tname = defsyms[trap_to_defsym(trap)].explanation;
+                pline("%s%s.", An(tname),
+                      (trap != MAGIC_PORTAL) ? "" : " to nowhere");
+            } else
+                pline("Creation of %s failed.", an(tname));
+            return &zeroobj;
+        }
+
+        /* furniture and terrain */
+        lev = &levl[x][y];
+        p = eos(bp);
+        if (!BSTRCMPI(bp, p - 8, "fountain")) {
+            lev->typ = FOUNTAIN;
+            level.flags.nfountains++;
+            if (!strncmpi(bp, "magic ", 6))
+                lev->blessedftn = 1;
+            pline("A %sfountain.", lev->blessedftn ? "magic " : "");
+            newsym(x, y);
+            return &zeroobj;
+        }
+        if (!BSTRCMPI(bp, p - 6, "throne")) {
+            lev->typ = THRONE;
+            pline("A throne.");
+            newsym(x, y);
+            return &zeroobj;
+        }
+        if (!BSTRCMPI(bp, p - 4, "sink")) {
+            lev->typ = SINK;
+            level.flags.nsinks++;
+            pline("A sink.");
+            newsym(x, y);
+            return &zeroobj;
+        }
+        /* ("water" matches "potion of water" rather than terrain) */
+        if (!BSTRCMPI(bp, p - 4, "pool") || !BSTRCMPI(bp, p - 4, "moat")) {
+            lev->typ = !BSTRCMPI(bp, p - 4, "pool") ? POOL : MOAT;
+            del_engr_at(x, y);
+            pline("A %s.", (lev->typ == POOL) ? "pool" : "moat");
+            /* Must manually make kelp! */
+            water_damage_chain(level.objects[x][y], TRUE);
+            newsym(x, y);
+            return &zeroobj;
+        }
+        if (!BSTRCMPI(bp, p - 4, "lava")) { /* also matches "molten lava" */
+            lev->typ = LAVAPOOL;
+            del_engr_at(x, y);
+            pline("A pool of molten lava.");
+            if (!(Levitation || Flying))
+                (void) lava_effects();
+            newsym(x, y);
+            return &zeroobj;
+        }
+
+        if (!BSTRCMPI(bp, p - 5, "altar")) {
+            aligntyp al;
+
+            lev->typ = ALTAR;
+            if (!strncmpi(bp, "chaotic ", 8))
+                al = A_CHAOTIC;
+            else if (!strncmpi(bp, "neutral ", 8))
+                al = A_NEUTRAL;
+            else if (!strncmpi(bp, "lawful ", 7))
+                al = A_LAWFUL;
+            else if (!strncmpi(bp, "unaligned ", 10))
+                al = A_NONE;
+            else /* -1 - A_CHAOTIC, 0 - A_NEUTRAL, 1 - A_LAWFUL */
+                al = (!rn2(6)) ? A_NONE : rn2((int) A_LAWFUL + 2) - 1;
+            lev->altarmask = Align2amask(al);
+            pline("%s altar.", An(align_str(al)));
+            newsym(x, y);
+            return &zeroobj;
+        }
+
+        if (!BSTRCMPI(bp, p - 5, "grave")
+            || !BSTRCMPI(bp, p - 9, "headstone")) {
+            make_grave(x, y, (char *) 0);
+            pline("%s.", IS_GRAVE(lev->typ) ? "A grave"
+                                            : "Can't place a grave here");
+            newsym(x, y);
+            return &zeroobj;
+        }
+
+        if (!BSTRCMPI(bp, p - 4, "tree")) {
+            lev->typ = TREE;
+            pline("A tree.");
+            newsym(x, y);
+            block_point(x, y);
+            return &zeroobj;
+        }
+
+        if (!BSTRCMPI(bp, p - 4, "bars")) {
+            lev->typ = IRONBARS;
+            pline("Iron bars.");
+            newsym(x, y);
+            return &zeroobj;
+        }
+    }
+
+    if (!oclass && !typ) {
+        if (!strncmpi(bp, "polearm", 7)) {
+            typ = rnd_otyp_by_wpnskill(P_POLEARMS);
+            goto typfnd;
+        } else if (!strncmpi(bp, "hammer", 6)) {
+            typ = rnd_otyp_by_wpnskill(P_HAMMER);
+            goto typfnd;
+        }
+    }
+
+    if (!oclass)
+        return ((struct obj *) 0);
+any:
+    if (!oclass)
+        oclass = wrpsym[rn2((int) sizeof(wrpsym))];
+typfnd:
+    if (typ)
+        oclass = objects[typ].oc_class;
+
+    /* handle some objects that are only allowed in wizard mode */
+    if (typ && !wizard) {
+        switch (typ) {
+        case AMULET_OF_YENDOR:
+            typ = FAKE_AMULET_OF_YENDOR;
+            break;
+        case CANDELABRUM_OF_INVOCATION:
+            typ = rnd_class(TALLOW_CANDLE, WAX_CANDLE);
+            break;
+        case BELL_OF_OPENING:
+            typ = BELL;
+            break;
+        case SPE_BOOK_OF_THE_DEAD:
+            typ = SPE_BLANK_PAPER;
+            break;
+        case MAGIC_LAMP:
+            typ = OIL_LAMP;
+            break;
+        default:
+            /* catch any other non-wishable objects (venom) */
+            if (objects[typ].oc_nowish)
+                return (struct obj *) 0;
+            break;
+        }
+    }
+
+    /*
+     * Create the object, then fine-tune it.
+     */
+    otmp = typ ? mksobj(typ, TRUE, FALSE) : mkobj(oclass, FALSE);
+    typ = otmp->otyp, oclass = otmp->oclass; /* what we actually got */
+
+    if (islit && (typ == OIL_LAMP || typ == MAGIC_LAMP || typ == BRASS_LANTERN
+                  || Is_candle(otmp) || typ == POT_OIL)) {
+        place_object(otmp, u.ux, u.uy); /* make it viable light source */
+        begin_burn(otmp, FALSE);
+        obj_extract_self(otmp); /* now release it for caller's use */
+    }
+
+    /* if player specified a reasonable count, maybe honor it */
+    if (cnt > 0 && objects[typ].oc_merge
+        && (wizard || cnt < rnd(6) || (cnt <= 7 && Is_candle(otmp))
+            || (cnt <= 20 && ((oclass == WEAPON_CLASS && is_ammo(otmp))
+                              || typ == ROCK || is_missile(otmp)))))
+        otmp->quan = (long) cnt;
+
+    if (oclass == VENOM_CLASS)
+        otmp->spe = 1;
+
+    if (spesgn == 0) {
+        spe = otmp->spe;
+    } else if (wizard) {
+        ; /* no alteration to spe */
+    } else if (oclass == ARMOR_CLASS || oclass == WEAPON_CLASS
+               || is_weptool(otmp)
+               || (oclass == RING_CLASS && objects[typ].oc_charged)) {
+        if (spe > rnd(5) && spe > otmp->spe)
+            spe = 0;
+        if (spe > 2 && Luck < 0)
+            spesgn = -1;
+    } else {
+        if (oclass == WAND_CLASS) {
+            if (spe > 1 && spesgn == -1)
+                spe = 1;
+        } else {
+            if (spe > 0 && spesgn == -1)
+                spe = 0;
+        }
+        if (spe > otmp->spe)
+            spe = otmp->spe;
+    }
+
+    if (spesgn == -1)
+        spe = -spe;
+
+    /* set otmp->spe.  This may, or may not, use spe... */
+    switch (typ) {
+    case TIN:
+        if (contents == EMPTY) {
+            otmp->corpsenm = NON_PM;
+            otmp->spe = 0;
+        } else if (contents == SPINACH) {
+            otmp->corpsenm = NON_PM;
+            otmp->spe = 1;
+        }
+        break;
+    case TOWEL:
+        if (wetness)
+            otmp->spe = wetness;
+        break;
+    case SLIME_MOLD:
+        otmp->spe = ftype;
+    /* Fall through */
+    case SKELETON_KEY:
+    case CHEST:
+    case LARGE_BOX:
+    case HEAVY_IRON_BALL:
+    case IRON_CHAIN:
+    case STATUE:
+        /* otmp->cobj already done in mksobj() */
+        break;
+#ifdef MAIL
+    case SCR_MAIL:
+        /* 0: delivered in-game via external event (or randomly for fake mail);
+           1: from bones or wishing; 2: written with marker */
+        otmp->spe = 1;
+        break;
+#endif
+    case WAN_WISHING:
+        if (!wizard) {
+            otmp->spe = (rn2(10) ? -1 : 0);
+            break;
+        }
+    /* fall through, if wizard */
+    default:
+        otmp->spe = spe;
+    }
+
+    /* set otmp->corpsenm or dragon scale [mail] */
+    if (mntmp >= LOW_PM) {
+        if (mntmp == PM_LONG_WORM_TAIL)
+            mntmp = PM_LONG_WORM;
+
+        switch (typ) {
+        case TIN:
+            otmp->spe = 0; /* No spinach */
+            if (dead_species(mntmp, FALSE)) {
+                otmp->corpsenm = NON_PM; /* it's empty */
+            } else if ((!(mons[mntmp].geno & G_UNIQ) || wizard)
+                       && !(mvitals[mntmp].mvflags & G_NOCORPSE)
+                       && mons[mntmp].cnutrit != 0) {
+                otmp->corpsenm = mntmp;
+            }
+            break;
+        case CORPSE:
+            if ((!(mons[mntmp].geno & G_UNIQ) || wizard)
+                && !(mvitals[mntmp].mvflags & G_NOCORPSE)) {
+                if (mons[mntmp].msound == MS_GUARDIAN)
+                    mntmp = genus(mntmp, 1);
+                set_corpsenm(otmp, mntmp);
+            }
+            break;
+        case EGG:
+            mntmp = can_be_hatched(mntmp);
+            /* this also sets hatch timer if appropriate */
+            set_corpsenm(otmp, mntmp);
+            break;
+        case FIGURINE:
+            if (!(mons[mntmp].geno & G_UNIQ) && !is_human(&mons[mntmp])
+#ifdef MAIL
+                && mntmp != PM_MAIL_DAEMON
+#endif
+                )
+                otmp->corpsenm = mntmp;
+            break;
+        case STATUE:
+            otmp->corpsenm = mntmp;
+            if (Has_contents(otmp) && verysmall(&mons[mntmp]))
+                delete_contents(otmp); /* no spellbook */
+            otmp->spe = ishistoric ? STATUE_HISTORIC : 0;
+            break;
+        case SCALE_MAIL:
+            /* Dragon mail - depends on the order of objects & dragons. */
+            if (mntmp >= PM_GRAY_DRAGON && mntmp <= PM_YELLOW_DRAGON)
+                otmp->otyp = GRAY_DRAGON_SCALE_MAIL + mntmp - PM_GRAY_DRAGON;
+            break;
+        }
+    }
+
+    /* set blessed/cursed -- setting the fields directly is safe
+     * since weight() is called below and addinv() will take care
+     * of luck */
+    if (iscursed) {
+        curse(otmp);
+    } else if (uncursed) {
+        otmp->blessed = 0;
+        otmp->cursed = (Luck < 0 && !wizard);
+    } else if (blessed) {
+        otmp->blessed = (Luck >= 0 || wizard);
+        otmp->cursed = (Luck < 0 && !wizard);
+    } else if (spesgn < 0) {
+        curse(otmp);
+    }
+
+    /* set eroded and erodeproof */
+    if (erosion_matters(otmp)) {
+        if (eroded && (is_flammable(otmp) || is_rustprone(otmp)))
+            otmp->oeroded = eroded;
+        if (eroded2 && (is_corrodeable(otmp) || is_rottable(otmp)))
+            otmp->oeroded2 = eroded2;
+        /*
+         * 3.6.1: earlier versions included `&& !eroded && !eroded2' here,
+         * but damageproof combined with damaged is feasible (eroded
+         * armor modified by confused reading of cursed destroy armor)
+         * so don't prevent player from wishing for such a combination.
+         */
+        if (erodeproof && (is_damageable(otmp) || otmp->otyp == CRYSKNIFE))
+            otmp->oerodeproof = (Luck >= 0 || wizard);
+    }
+
+    /* set otmp->recharged */
+    if (oclass == WAND_CLASS) {
+        /* prevent wishing abuse */
+        if (otmp->otyp == WAN_WISHING && !wizard)
+            rechrg = 1;
+        otmp->recharged = (unsigned) rechrg;
+    }
+
+    /* set poisoned */
+    if (ispoisoned) {
+        if (is_poisonable(otmp))
+            otmp->opoisoned = (Luck >= 0);
+        else if (oclass == FOOD_CLASS)
+            /* try to taint by making it as old as possible */
+            otmp->age = 1L;
+    }
+    /* and [un]trapped */
+    if (trapped) {
+        if (Is_box(otmp) || typ == TIN)
+            otmp->otrapped = (trapped == 1);
+    }
+
+    if (isgreased)
+        otmp->greased = 1;
+
+    if (isdiluted && otmp->oclass == POTION_CLASS && otmp->otyp != POT_WATER)
+        otmp->odiluted = 1;
+
+    /* set tin variety */
+    if (otmp->otyp == TIN && tvariety >= 0 && (rn2(4) || wizard))
+        set_tin_variety(otmp, tvariety);
+
+    if (name) {
+        const char *aname;
+        short objtyp;
+
+        /* an artifact name might need capitalization fixing */
+        aname = artifact_name(name, &objtyp);
+        if (aname && objtyp == otmp->otyp)
+            name = aname;
+
+        /* 3.6 tribute - fix up novel */
+        if (otmp->otyp == SPE_NOVEL) {
+            const char *novelname;
+
+            novelname = lookup_novel(name, &otmp->novelidx);
+            if (novelname)
+                name = novelname;
+        }
+
+        otmp = oname(otmp, name);
+        /* name==aname => wished for artifact (otmp->oartifact => got it) */
+        if (otmp->oartifact || name == aname) {
+            otmp->quan = 1L;
+            u.uconduct.wisharti++; /* KMH, conduct */
+        }
+    }
+
+    /* more wishing abuse: don't allow wishing for certain artifacts */
+    /* and make them pay; charge them for the wish anyway! */
+    if ((is_quest_artifact(otmp)
+         || (otmp->oartifact && rn2(nartifact_exist()) > 1)) && !wizard) {
+        artifact_exists(otmp, safe_oname(otmp), FALSE);
+        obfree(otmp, (struct obj *) 0);
+        otmp = &zeroobj;
+        pline("For a moment, you feel %s in your %s, but it disappears!",
+              something, makeplural(body_part(HAND)));
+    }
+
+    if (halfeaten && otmp->oclass == FOOD_CLASS) {
+        if (otmp->otyp == CORPSE)
+            otmp->oeaten = mons[otmp->corpsenm].cnutrit;
+        else
+            otmp->oeaten = objects[otmp->otyp].oc_nutrition;
+        /* (do this adjustment before setting up object's weight) */
+        consume_oeaten(otmp, 1);
+    }
+    otmp->owt = weight(otmp);
+    if (very && otmp->otyp == HEAVY_IRON_BALL)
+        otmp->owt += IRON_BALL_W_INCR;
+    else if (gsize > 1 && otmp->globby)
+        /* 0: unspecified => small; 1: small => keep default owt of 20;
+           2: medium => 120; 3: large => 320; 4: very large => 520 */
+        otmp->owt += 100 + (gsize - 2) * 200;
+
+    return otmp;
+}
+
+int
+rnd_class(first, last)
+int first, last;
+{
+    int i, x, sum = 0;
+
+    if (first == last)
+        return first;
+    for (i = first; i <= last; i++)
+        sum += objects[i].oc_prob;
+    if (!sum) /* all zero */
+        return first + rn2(last - first + 1);
+    x = rnd(sum);
+    for (i = first; i <= last; i++)
+        if (objects[i].oc_prob && (x -= objects[i].oc_prob) <= 0)
+            return i;
+    return 0;
+}
+
+STATIC_OVL const char *
+Japanese_item_name(i)
+int i;
+{
+    struct Jitem *j = Japanese_items;
+
+    while (j->item) {
+        if (i == j->item)
+            return j->name;
+        j++;
+    }
+    return (const char *) 0;
+}
+
+const char *
+suit_simple_name(suit)
+struct obj *suit;
+{
+    const char *suitnm, *esuitp;
+
+    if (Is_dragon_mail(suit))
+        return "dragon mail"; /* <color> dragon scale mail */
+    else if (Is_dragon_scales(suit))
+        return "dragon scales";
+    suitnm = OBJ_NAME(objects[suit->otyp]);
+    esuitp = eos((char *) suitnm);
+    if (strlen(suitnm) > 5 && !strcmp(esuitp - 5, " mail"))
+        return "mail"; /* most suits fall into this category */
+    else if (strlen(suitnm) > 7 && !strcmp(esuitp - 7, " jacket"))
+        return "jacket"; /* leather jacket */
+    /* suit is lame but armor is ambiguous and body armor is absurd */
+    return "suit";
+}
+
+const char *
+cloak_simple_name(cloak)
+struct obj *cloak;
+{
+    if (cloak) {
+        switch (cloak->otyp) {
+        case ROBE:
+            return "robe";
+        case MUMMY_WRAPPING:
+            return "wrapping";
+        case ALCHEMY_SMOCK:
+            return (objects[cloak->otyp].oc_name_known && cloak->dknown)
+                       ? "smock"
+                       : "apron";
+        default:
+            break;
+        }
+    }
+    return "cloak";
+}
+
+/* helm vs hat for messages */
+const char *
+helm_simple_name(helmet)
+struct obj *helmet;
+{
+    /*
+     *  There is some wiggle room here; the result has been chosen
+     *  for consistency with the "protected by hard helmet" messages
+     *  given for various bonks on the head:  headgear that provides
+     *  such protection is a "helm", that which doesn't is a "hat".
+     *
+     *      elven leather helm / leather hat    -> hat
+     *      dwarvish iron helm / hard hat       -> helm
+     *  The rest are completely straightforward:
+     *      fedora, cornuthaum, dunce cap       -> hat
+     *      all other types of helmets          -> helm
+     */
+    return (helmet && !is_metallic(helmet)) ? "hat" : "helm";
+}
+
+const char *
+mimic_obj_name(mtmp)
+struct monst *mtmp;
+{
+    if (mtmp->m_ap_type == M_AP_OBJECT) {
+        if (mtmp->mappearance == GOLD_PIECE)
+            return "gold";
+        if (mtmp->mappearance != STRANGE_OBJECT)
+            return simple_typename(mtmp->mappearance);
+    }
+    return "whatcha-may-callit";
+}
+
+/*
+ * Construct a query prompt string, based around an object name, which is
+ * guaranteed to fit within [QBUFSZ].  Takes an optional prefix, three
+ * choices for filling in the middle (two object formatting functions and a
+ * last resort literal which should be very short), and an optional suffix.
+ */
+char *
+safe_qbuf(qbuf, qprefix, qsuffix, obj, func, altfunc, lastR)
+char *qbuf; /* output buffer */
+const char *qprefix, *qsuffix;
+struct obj *obj;
+char *FDECL((*func), (OBJ_P)), *FDECL((*altfunc), (OBJ_P));
+const char *lastR;
+{
+    char *bufp, *endp;
+    /* convert size_t (or int for ancient systems) to ordinary unsigned */
+    unsigned len, lenlimit,
+        len_qpfx = (unsigned) (qprefix ? strlen(qprefix) : 0),
+        len_qsfx = (unsigned) (qsuffix ? strlen(qsuffix) : 0),
+        len_lastR = (unsigned) strlen(lastR);
+
+    lenlimit = QBUFSZ - 1;
+    endp = qbuf + lenlimit;
+    /* sanity check, aimed mainly at paniclog (it's conceivable for
+       the result of short_oname() to be shorter than the length of
+       the last resort string, but we ignore that possibility here) */
+    if (len_qpfx > lenlimit)
+        impossible("safe_qbuf: prefix too long (%u characters).", len_qpfx);
+    else if (len_qpfx + len_qsfx > lenlimit)
+        impossible("safe_qbuf: suffix too long (%u + %u characters).",
+                   len_qpfx, len_qsfx);
+    else if (len_qpfx + len_lastR + len_qsfx > lenlimit)
+        impossible("safe_qbuf: filler too long (%u + %u + %u characters).",
+                   len_qpfx, len_lastR, len_qsfx);
+
+    /* the output buffer might be the same as the prefix if caller
+       has already partially filled it */
+    if (qbuf == qprefix) {
+        /* prefix is already in the buffer */
+        *endp = '\0';
+    } else if (qprefix) {
+        /* put prefix into the buffer */
+        (void) strncpy(qbuf, qprefix, lenlimit);
+        *endp = '\0';
+    } else {
+        /* no prefix; output buffer starts out empty */
+        qbuf[0] = '\0';
+    }
+    len = (unsigned) strlen(qbuf);
+
+    if (len + len_lastR + len_qsfx > lenlimit) {
+        /* too long; skip formatting, last resort output is truncated */
+        if (len < lenlimit) {
+            (void) strncpy(&qbuf[len], lastR, lenlimit - len);
+            *endp = '\0';
+            len = (unsigned) strlen(qbuf);
+            if (qsuffix && len < lenlimit) {
+                (void) strncpy(&qbuf[len], qsuffix, lenlimit - len);
+                *endp = '\0';
+                /* len = (unsigned) strlen(qbuf); */
+            }
+        }
+    } else {
+        /* suffix and last resort are guaranteed to fit */
+        len += len_qsfx; /* include the pending suffix */
+        /* format the object */
+        bufp = short_oname(obj, func, altfunc, lenlimit - len);
+        if (len + strlen(bufp) <= lenlimit)
+            Strcat(qbuf, bufp); /* formatted name fits */
+        else
+            Strcat(qbuf, lastR); /* use last resort */
+        releaseobuf(bufp);
+
+        if (qsuffix)
+            Strcat(qbuf, qsuffix);
+    }
+    /* assert( strlen(qbuf) < QBUFSZ ); */
+    return qbuf;
+}
+
+/*objnam.c*/