From: nhmall Date: Sun, 18 Mar 2018 13:54:18 +0000 (-0400) Subject: another dictionary consulted, another couple of prefixes X-Git-Tag: NetHack-3.6.1_RC01~104 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1ab2c25c9a0d7cc58e1c49636096bb66bdc3851f;p=nethack another dictionary consulted, another couple of prefixes --- diff --git a/objnam.c b/objnam.c new file mode 100644 index 000000000..ecb5de377 --- /dev/null +++ b/objnam.c @@ -0,0 +1,4062 @@ +/* NetHack 3.6 objnam.c $NHDT-Date: 1521377345 2018/03/18 12:49:05 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.194 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +/* "an uncursed greased partly eaten guardian naga hatchling [corpse]" */ +#define PREFIX 80 /* (56) */ +#define SCHAR_LIM 127 +#define NUMOBUF 12 + +STATIC_DCL char *FDECL(strprepend, (char *, const char *)); +STATIC_DCL short FDECL(rnd_otyp_by_wpnskill, (SCHAR_P)); +STATIC_DCL short FDECL(rnd_otyp_by_namedesc, (char *, CHAR_P)); +STATIC_DCL boolean FDECL(wishymatch, (const char *, const char *, BOOLEAN_P)); +STATIC_DCL char *NDECL(nextobuf); +STATIC_DCL void FDECL(releaseobuf, (char *)); +STATIC_DCL char *FDECL(minimal_xname, (struct obj *)); +STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *)); +STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned)); +STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P, + const char *const *)); +STATIC_DCL char *FDECL(singplur_compound, (char *)); +STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned)); +STATIC_DCL boolean FDECL(badman, (const char *, BOOLEAN_P)); + +struct Jitem { + int item; + const char *name; +}; + +#define BSTRCMPI(base, ptr, str) ((ptr) < base || strcmpi((ptr), str)) +#define BSTRNCMPI(base, ptr, str, num) \ + ((ptr) < base || strncmpi((ptr), str, num)) +#define Strcasecpy(dst, src) (void) strcasecpy(dst, src) + +/* true for gems/rocks that should have " stone" appended to their names */ +#define GemStone(typ) \ + (typ == FLINT \ + || (objects[typ].oc_material == GEMSTONE \ + && (typ != DILITHIUM_CRYSTAL && typ != RUBY && typ != DIAMOND \ + && typ != SAPPHIRE && typ != BLACK_OPAL && typ != EMERALD \ + && typ != OPAL))) + +STATIC_OVL struct Jitem Japanese_items[] = { { SHORT_SWORD, "wakizashi" }, + { BROADSWORD, "ninja-to" }, + { FLAIL, "nunchaku" }, + { GLAIVE, "naginata" }, + { LOCK_PICK, "osaku" }, + { WOODEN_HARP, "koto" }, + { KNIFE, "shito" }, + { PLATE_MAIL, "tanko" }, + { HELMET, "kabuto" }, + { LEATHER_GLOVES, "yugake" }, + { FOOD_RATION, "gunyoki" }, + { POT_BOOZE, "sake" }, + { 0, "" } }; + +STATIC_DCL const char *FDECL(Japanese_item_name, (int i)); + +STATIC_OVL char * +strprepend(s, pref) +register char *s; +register const char *pref; +{ + register int i = (int) strlen(pref); + + if (i > PREFIX) { + impossible("PREFIX too short (for %d).", i); + return s; + } + s -= i; + (void) strncpy(s, pref, i); /* do not copy trailing 0 */ + return s; +} + +/* manage a pool of BUFSZ buffers, so callers don't have to */ +static char NEARDATA obufs[NUMOBUF][BUFSZ]; +static int obufidx = 0; + +STATIC_OVL char * +nextobuf() +{ + obufidx = (obufidx + 1) % NUMOBUF; + return obufs[obufidx]; +} + +/* put the most recently allocated buffer back if possible */ +STATIC_OVL void +releaseobuf(bufp) +char *bufp; +{ + /* caller may not know whether bufp is the most recently allocated + buffer; if it isn't, do nothing; note that because of the somewhat + obscure PREFIX handling for object name formatting by xname(), + the pointer our caller has and is passing to us might be into the + middle of an obuf rather than the address returned by nextobuf() */ + if (bufp >= obufs[obufidx] + && bufp < obufs[obufidx] + sizeof obufs[obufidx]) /* obufs[][BUFSZ] */ + obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF; +} + +char * +obj_typename(otyp) +register int otyp; +{ + char *buf = nextobuf(); + struct objclass *ocl = &objects[otyp]; + const char *actualn = OBJ_NAME(*ocl); + const char *dn = OBJ_DESCR(*ocl); + const char *un = ocl->oc_uname; + int nn = ocl->oc_name_known; + + if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp)) + actualn = Japanese_item_name(otyp); + switch (ocl->oc_class) { + case COIN_CLASS: + Strcpy(buf, "coin"); + break; + case POTION_CLASS: + Strcpy(buf, "potion"); + break; + case SCROLL_CLASS: + Strcpy(buf, "scroll"); + break; + case WAND_CLASS: + Strcpy(buf, "wand"); + break; + case SPBOOK_CLASS: + if (otyp != SPE_NOVEL) { + Strcpy(buf, "spellbook"); + } else { + Strcpy(buf, !nn ? "book" : "novel"); + nn = 0; + } + break; + case RING_CLASS: + Strcpy(buf, "ring"); + break; + case AMULET_CLASS: + if (nn) + Strcpy(buf, actualn); + else + Strcpy(buf, "amulet"); + if (un) + Sprintf(eos(buf), " called %s", un); + if (dn) + Sprintf(eos(buf), " (%s)", dn); + return buf; + default: + if (nn) { + Strcpy(buf, actualn); + if (GemStone(otyp)) + Strcat(buf, " stone"); + if (un) + Sprintf(eos(buf), " called %s", un); + if (dn) + Sprintf(eos(buf), " (%s)", dn); + } else { + Strcpy(buf, dn ? dn : actualn); + if (ocl->oc_class == GEM_CLASS) + Strcat(buf, + (ocl->oc_material == MINERAL) ? " stone" : " gem"); + if (un) + Sprintf(eos(buf), " called %s", un); + } + return buf; + } + /* here for ring/scroll/potion/wand */ + if (nn) { + if (ocl->oc_unique) + Strcpy(buf, actualn); /* avoid spellbook of Book of the Dead */ + else + Sprintf(eos(buf), " of %s", actualn); + } + if (un) + Sprintf(eos(buf), " called %s", un); + if (dn) + Sprintf(eos(buf), " (%s)", dn); + return buf; +} + +/* less verbose result than obj_typename(); either the actual name + or the description (but not both); user-assigned name is ignored */ +char * +simple_typename(otyp) +int otyp; +{ + char *bufp, *pp, *save_uname = objects[otyp].oc_uname; + + objects[otyp].oc_uname = 0; /* suppress any name given by user */ + bufp = obj_typename(otyp); + objects[otyp].oc_uname = save_uname; + if ((pp = strstri(bufp, " (")) != 0) + *pp = '\0'; /* strip the appended description */ + return bufp; +} + +boolean +obj_is_pname(obj) +struct obj *obj; +{ + if (!obj->oartifact || !has_oname(obj)) + return FALSE; + if (!program_state.gameover && !iflags.override_ID) { + if (not_fully_identified(obj)) + return FALSE; + } + return TRUE; +} + +/* used by distant_name() to pass extra information to xname_flags(); + it would be much cleaner if this were a parameter, but that would + require all of the xname() and doname() calls to be modified */ +static int distantname = 0; + +/* Give the name of an object seen at a distance. Unlike xname/doname, + * we don't want to set dknown if it's not set already. + */ +char * +distant_name(obj, func) +struct obj *obj; +char *FDECL((*func), (OBJ_P)); +{ + char *str; + + /* 3.6.1: this used to save Blind, set it, make the call, then restore + * the saved value; but the Eyes of the Overworld override blindness + * and let characters wearing them get dknown set for distant items. + * + * TODO? if the hero is wearing those Eyes, figure out whether the + * object is within X-ray radius and only treat it as distant when + * beyond that radius. Logic is iffy but result might be interesting. + */ + ++distantname; + str = (*func)(obj); + --distantname; + return str; +} + +/* convert player specified fruit name into corresponding fruit juice name + ("slice of pizza" -> "pizza juice" rather than "slice of pizza juice") */ +char * +fruitname(juice) +boolean juice; /* whether or not to append " juice" to the name */ +{ + char *buf = nextobuf(); + const char *fruit_nam = strstri(pl_fruit, " of "); + + if (fruit_nam) + fruit_nam += 4; /* skip past " of " */ + else + fruit_nam = pl_fruit; /* use it as is */ + + Sprintf(buf, "%s%s", makesingular(fruit_nam), juice ? " juice" : ""); + return buf; +} + +/* look up a named fruit by index (1..127) */ +struct fruit * +fruit_from_indx(indx) +int indx; +{ + struct fruit *f; + + for (f = ffruit; f; f = f->nextf) + if (f->fid == indx) + break; + return f; +} + +/* look up a named fruit by name */ +struct fruit * +fruit_from_name(fname, exact, highest_fid) +const char *fname; +boolean exact; /* False => prefix or exact match, True = exact match only */ +int *highest_fid; /* optional output; only valid if 'fname' isn't found */ +{ + struct fruit *f, *tentativef; + char *altfname; + unsigned k; + /* + * note: named fruits are case-senstive... + */ + + if (highest_fid) + *highest_fid = 0; + /* first try for an exact match */ + for (f = ffruit; f; f = f->nextf) + if (!strcmp(f->fname, fname)) + return f; + else if (highest_fid && f->fid > *highest_fid) + *highest_fid = f->fid; + + /* didn't match as-is; if caller is willing to accept a prefix + match, try to find one; we want to find the longest prefix that + matches, not the first */ + if (!exact) { + tentativef = 0; + for (f = ffruit; f; f = f->nextf) { + k = strlen(f->fname); + if (!strncmp(f->fname, fname, k) + && (!fname[k] || fname[k] == ' ') + && (!tentativef || k > strlen(tentativef->fname))) + tentativef = f; + } + f = tentativef; + } + /* if we still don't have a match, try singularizing the target; + for exact match, that's trivial, but for prefix, it's hard */ + if (!f) { + altfname = makesingular(fname); + for (f = ffruit; f; f = f->nextf) { + if (!strcmp(f->fname, altfname)) + break; + } + releaseobuf(altfname); + } + if (!f && !exact) { + char fnamebuf[BUFSZ], *p; + unsigned fname_k = strlen(fname); /* length of assumed plural fname */ + + tentativef = 0; + for (f = ffruit; f; f = f->nextf) { + k = strlen(f->fname); + /* reload fnamebuf[] each iteration in case it gets modified; + there's no need to recalculate fname_k */ + Strcpy(fnamebuf, fname); + /* bug? if singular of fname is longer than plural, + failing the 'fname_k > k' test could skip a viable + candidate; unfortunately, we can't singularize until + after stripping off trailing stuff and we can't get + accurate fname_k until fname has been singularized; + compromise and use 'fname_k >= k' instead of '>', + accepting 1 char length discrepancy without risking + false match (I hope...) */ + if (fname_k >= k && (p = index(&fnamebuf[k], ' ')) != 0) { + *p = '\0'; /* truncate at 1st space past length of f->fname */ + altfname = makesingular(fnamebuf); + k = strlen(altfname); /* actually revised 'fname_k' */ + if (!strcmp(f->fname, altfname) + && (!tentativef || k > strlen(tentativef->fname))) + tentativef = f; + releaseobuf(altfname); /* avoid churning through all obufs */ + } + } + f = tentativef; + } + return f; +} + +/* sort the named-fruit linked list by fruit index number */ +void +reorder_fruit(forward) +boolean forward; +{ + struct fruit *f, *allfr[1 + 127]; + int i, j, k = SIZE(allfr); + + for (i = 0; i < k; ++i) + allfr[i] = (struct fruit *) 0; + for (f = ffruit; f; f = f->nextf) { + /* without sanity checking, this would reduce to 'allfr[f->fid]=f' */ + j = f->fid; + if (j < 1 || j >= k) { + impossible("reorder_fruit: fruit index (%d) out of range", j); + return; /* don't sort after all; should never happen... */ + } else if (allfr[j]) { + impossible("reorder_fruit: duplicate fruit index (%d)", j); + return; + } + allfr[j] = f; + } + ffruit = 0; /* reset linked list; we're rebuilding it from scratch */ + /* slot [0] will always be empty; must start 'i' at 1 to avoid + [k - i] being out of bounds during first iteration */ + for (i = 1; i < k; ++i) { + /* for forward ordering, go through indices from high to low; + for backward ordering, go from low to high */ + j = forward ? (k - i) : i; + if (allfr[j]) { + allfr[j]->nextf = ffruit; + ffruit = allfr[j]; + } + } +} + +char * +xname(obj) +struct obj *obj; +{ + return xname_flags(obj, CXN_NORMAL); +} + +char * +xname_flags(obj, cxn_flags) +register struct obj *obj; +unsigned cxn_flags; /* bitmask of CXN_xxx values */ +{ + register char *buf; + register int typ = obj->otyp; + register struct objclass *ocl = &objects[typ]; + int nn = ocl->oc_name_known, omndx = obj->corpsenm; + const char *actualn = OBJ_NAME(*ocl); + const char *dn = OBJ_DESCR(*ocl) ? OBJ_DESCR(*ocl) : actualn; + const char *un = ocl->oc_uname; + boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR); + boolean known, dknown, bknown; + + buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */ + if (Role_if(PM_SAMURAI) && Japanese_item_name(typ)) + actualn = Japanese_item_name(typ); + + buf[0] = '\0'; + /* + * clean up known when it's tied to oc_name_known, eg after AD_DRIN + * This is only required for unique objects since the article + * printed for the object is tied to the combination of the two + * and printing the wrong article gives away information. + */ + if (!nn && ocl->oc_uses_known && ocl->oc_unique) + obj->known = 0; + if (!Blind && !distantname) + obj->dknown = TRUE; + if (Role_if(PM_PRIEST)) + obj->bknown = TRUE; + + if (iflags.override_ID) { + known = dknown = bknown = TRUE; + nn = 1; + } else { + known = obj->known; + dknown = obj->dknown; + bknown = obj->bknown; + } + + if (obj_is_pname(obj)) + goto nameit; + switch (obj->oclass) { + case AMULET_CLASS: + if (!dknown) + Strcpy(buf, "amulet"); + else if (typ == AMULET_OF_YENDOR || typ == FAKE_AMULET_OF_YENDOR) + /* each must be identified individually */ + Strcpy(buf, known ? actualn : dn); + else if (nn) + Strcpy(buf, actualn); + else if (un) + Sprintf(buf, "amulet called %s", un); + else + Sprintf(buf, "%s amulet", dn); + break; + case WEAPON_CLASS: + if (is_poisonable(obj) && obj->opoisoned) + Strcpy(buf, "poisoned "); + case VENOM_CLASS: + case TOOL_CLASS: + if (typ == LENSES) + Strcpy(buf, "pair of "); + else if (is_wet_towel(obj)) + Strcpy(buf, (obj->spe < 3) ? "moist " : "wet "); + + if (!dknown) + Strcat(buf, dn); + else if (nn) + Strcat(buf, actualn); + else if (un) { + Strcat(buf, dn); + Strcat(buf, " called "); + Strcat(buf, un); + } else + Strcat(buf, dn); + /* If we use an() here we'd have to remember never to use */ + /* it whenever calling doname() or xname(). */ + if (typ == FIGURINE && omndx != NON_PM) { + Sprintf(eos(buf), " of a%s %s", + index(vowels, *mons[omndx].mname) ? "n" : "", + mons[omndx].mname); + } else if (is_wet_towel(obj)) { + if (wizard) + Sprintf(eos(buf), " (%d)", obj->spe); + } + break; + case ARMOR_CLASS: + /* depends on order of the dragon scales objects */ + if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) { + Sprintf(buf, "set of %s", actualn); + break; + } + if (is_boots(obj) || is_gloves(obj)) + Strcpy(buf, "pair of "); + + if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD + && !dknown) { + Strcpy(buf, "shield"); + break; + } + if (obj->otyp == SHIELD_OF_REFLECTION && !dknown) { + Strcpy(buf, "smooth shield"); + break; + } + + if (nn) + Strcat(buf, actualn); + else if (un) { + if (is_boots(obj)) + Strcat(buf, "boots"); + else if (is_gloves(obj)) + Strcat(buf, "gloves"); + else if (is_cloak(obj)) + Strcpy(buf, "cloak"); + else if (is_helmet(obj)) + Strcpy(buf, "helmet"); + else if (is_shield(obj)) + Strcpy(buf, "shield"); + else + Strcpy(buf, "armor"); + Strcat(buf, " called "); + Strcat(buf, un); + } else + Strcat(buf, dn); + break; + case FOOD_CLASS: + if (typ == SLIME_MOLD) { + struct fruit *f = fruit_from_indx(obj->spe); + + if (!f) { + impossible("Bad fruit #%d?", obj->spe); + Strcpy(buf, "fruit"); + } else { + Strcpy(buf, f->fname); + if (pluralize) { + /* ick; already pluralized fruit names + are allowed--we want to try to avoid + adding a redundant plural suffix */ + Strcpy(buf, makeplural(makesingular(buf))); + pluralize = FALSE; + } + } + break; + } + if (obj->globby) { + Sprintf(buf, "%s%s", + (obj->owt <= 100) + ? "small " + : (obj->owt > 500) + ? "very large " + : (obj->owt > 300) + ? "large " + : "", + actualn); + break; + } + + Strcpy(buf, actualn); + if (typ == TIN && known) + tin_details(obj, omndx, buf); + break; + case COIN_CLASS: + case CHAIN_CLASS: + Strcpy(buf, actualn); + break; + case ROCK_CLASS: + if (typ == STATUE && omndx != NON_PM) + Sprintf(buf, "%s%s of %s%s", + (Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC)) + ? "historic " + : "", + actualn, + type_is_pname(&mons[omndx]) + ? "" + : the_unique_pm(&mons[omndx]) + ? "the " + : index(vowels, *mons[omndx].mname) + ? "an " + : "a ", + mons[omndx].mname); + else + Strcpy(buf, actualn); + break; + case BALL_CLASS: + Sprintf(buf, "%sheavy iron ball", + (obj->owt > ocl->oc_weight) ? "very " : ""); + break; + case POTION_CLASS: + if (dknown && obj->odiluted) + Strcpy(buf, "diluted "); + if (nn || un || !dknown) { + Strcat(buf, "potion"); + if (!dknown) + break; + if (nn) { + Strcat(buf, " of "); + if (typ == POT_WATER && bknown + && (obj->blessed || obj->cursed)) { + Strcat(buf, obj->blessed ? "holy " : "unholy "); + } + Strcat(buf, actualn); + } else { + Strcat(buf, " called "); + Strcat(buf, un); + } + } else { + Strcat(buf, dn); + Strcat(buf, " potion"); + } + break; + case SCROLL_CLASS: + Strcpy(buf, "scroll"); + if (!dknown) + break; + if (nn) { + Strcat(buf, " of "); + Strcat(buf, actualn); + } else if (un) { + Strcat(buf, " called "); + Strcat(buf, un); + } else if (ocl->oc_magic) { + Strcat(buf, " labeled "); + Strcat(buf, dn); + } else { + Strcpy(buf, dn); + Strcat(buf, " scroll"); + } + break; + case WAND_CLASS: + if (!dknown) + Strcpy(buf, "wand"); + else if (nn) + Sprintf(buf, "wand of %s", actualn); + else if (un) + Sprintf(buf, "wand called %s", un); + else + Sprintf(buf, "%s wand", dn); + break; + case SPBOOK_CLASS: + if (typ == SPE_NOVEL) { /* 3.6 tribute */ + if (!dknown) + Strcpy(buf, "book"); + else if (nn) + Strcpy(buf, actualn); + else if (un) + Sprintf(buf, "novel called %s", un); + else + Sprintf(buf, "%s book", dn); + break; + /* end of tribute */ + } else if (!dknown) { + Strcpy(buf, "spellbook"); + } else if (nn) { + if (typ != SPE_BOOK_OF_THE_DEAD) + Strcpy(buf, "spellbook of "); + Strcat(buf, actualn); + } else if (un) { + Sprintf(buf, "spellbook called %s", un); + } else + Sprintf(buf, "%s spellbook", dn); + break; + case RING_CLASS: + if (!dknown) + Strcpy(buf, "ring"); + else if (nn) + Sprintf(buf, "ring of %s", actualn); + else if (un) + Sprintf(buf, "ring called %s", un); + else + Sprintf(buf, "%s ring", dn); + break; + case GEM_CLASS: { + const char *rock = (ocl->oc_material == MINERAL) ? "stone" : "gem"; + + if (!dknown) { + Strcpy(buf, rock); + } else if (!nn) { + if (un) + Sprintf(buf, "%s called %s", rock, un); + else + Sprintf(buf, "%s %s", dn, rock); + } else { + Strcpy(buf, actualn); + if (GemStone(typ)) + Strcat(buf, " stone"); + } + break; + } + default: + Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe); + } + if (pluralize) + Strcpy(buf, makeplural(buf)); + + if (obj->otyp == T_SHIRT && program_state.gameover) { + char tmpbuf[BUFSZ]; + + Sprintf(eos(buf), " with text \"%s\"", tshirt_text(obj, tmpbuf)); + } + + if (has_oname(obj) && dknown) { + Strcat(buf, " named "); + nameit: + Strcat(buf, ONAME(obj)); + } + + if (!strncmpi(buf, "the ", 4)) + buf += 4; + return buf; +} + +/* similar to simple_typename but minimal_xname operates on a particular + object rather than its general type; it formats the most basic info: + potion -- if description not known + brown potion -- if oc_name_known not set + potion of object detection -- if discovered + */ +STATIC_OVL char * +minimal_xname(obj) +struct obj *obj; +{ + char *bufp; + struct obj bareobj; + struct objclass saveobcls; + int otyp = obj->otyp; + + /* suppress user-supplied name */ + saveobcls.oc_uname = objects[otyp].oc_uname; + objects[otyp].oc_uname = 0; + /* suppress actual name if object's description is unknown */ + saveobcls.oc_name_known = objects[otyp].oc_name_known; + if (!obj->dknown) + objects[otyp].oc_name_known = 0; + + /* caveat: this makes a lot of assumptions about which fields + are required in order for xname() to yield a sensible result */ + bareobj = zeroobj; + bareobj.otyp = otyp; + bareobj.oclass = obj->oclass; + bareobj.dknown = obj->dknown; + /* suppress known except for amulets (needed for fakes and real A-of-Y) */ + bareobj.known = (obj->oclass == AMULET_CLASS) + ? obj->known + /* default is "on" for types which don't use it */ + : !objects[otyp].oc_uses_known; + bareobj.quan = 1L; /* don't want plural */ + bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */ + /* but suppressing fruit details leads to "bad fruit #0" + [perhaps we should force "slime mold" rather than use xname?] */ + if (obj->otyp == SLIME_MOLD) + bareobj.spe = obj->spe; + + bufp = distant_name(&bareobj, xname); /* xname(&bareobj) */ + if (!strncmp(bufp, "uncursed ", 9)) + bufp += 9; /* Role_if(PM_PRIEST) */ + + objects[otyp].oc_uname = saveobcls.oc_uname; + objects[otyp].oc_name_known = saveobcls.oc_name_known; + return bufp; +} + +/* xname() output augmented for multishot missile feedback */ +char * +mshot_xname(obj) +struct obj *obj; +{ + char tmpbuf[BUFSZ]; + char *onm = xname(obj); + + if (m_shot.n > 1 && m_shot.o == obj->otyp) { + /* "the Nth arrow"; value will eventually be passed to an() or + The(), both of which correctly handle this "the " prefix */ + Sprintf(tmpbuf, "the %d%s ", m_shot.i, ordin(m_shot.i)); + onm = strprepend(onm, tmpbuf); + } + return onm; +} + +/* used for naming "the unique_item" instead of "a unique_item" */ +boolean +the_unique_obj(obj) +struct obj *obj; +{ + boolean known = (obj->known || iflags.override_ID); + + if (!obj->dknown && !iflags.override_ID) + return FALSE; + else if (obj->otyp == FAKE_AMULET_OF_YENDOR && !known) + return TRUE; /* lie */ + else + return (boolean) (objects[obj->otyp].oc_unique + && (known || obj->otyp == AMULET_OF_YENDOR)); +} + +/* should monster type be prefixed with "the"? (mostly used for corpses) */ +boolean +the_unique_pm(ptr) +struct permonst *ptr; +{ + boolean uniq; + + /* even though monsters with personal names are unique, we want to + describe them as "Name" rather than "the Name" */ + if (type_is_pname(ptr)) + return FALSE; + + uniq = (ptr->geno & G_UNIQ) ? TRUE : FALSE; + /* high priest is unique if it includes "of ", 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 " */ + } 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 " would be misleading when poison is + not the cause of death and "poisoned by poisoned " 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 "(s) of ", 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 + 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 ". For "3 ", 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 "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 " vs " detection" */ + if ((p = strstri(u_str, SP_detection)) != 0 + && !*(p + sizeof SP_detection - 1)) { + /* convert " detection" into "detect " */ + *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, " detection" vs "detect " */ + if (!strncmpi(u_str, detect_SP, sizeof detect_SP - 1)) { + /* convert "detect s" into " 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 , 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 " or " object" */ + typ = beartrap ? BEARTRAP : LAND_MINE; + goto typfnd; + } else if (trapped == 1 || *zp != '\0') { + /* "trapped " or " trap" (actually "*") */ + 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"; /* 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*/