--- /dev/null
+/* 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*/