Restricting the text display only to the end of game disclose, so it doesn't clutter the inventory during gameplay and so that the readability of t-shirts is not given away.
3410 lines
97 KiB
C
3410 lines
97 KiB
C
/* NetHack 3.5 objnam.c $NHDT-Date: 1427440866 2015/03/27 07:21:06 $ $NHDT-Branch: master $:$NHDT-Revision: 1.109 $ */
|
|
/* NetHack 3.5 objnam.c $Date: 2011/10/27 02:24:54 $ $Revision: 1.101 $ */
|
|
/* 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 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 boolean FDECL(singplur_lookup, (char *,char *,BOOLEAN_P,
|
|
const char *const *));
|
|
STATIC_DCL char *FDECL(singplur_compound, (char *));
|
|
|
|
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 */
|
|
if (bufp == obufs[obufidx])
|
|
obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF;
|
|
}
|
|
|
|
char *
|
|
obj_typename(otyp)
|
|
register int otyp;
|
|
{
|
|
char *buf = nextobuf();
|
|
register struct objclass *ocl = &objects[otyp];
|
|
register const char *actualn = OBJ_NAME(*ocl);
|
|
register const char *dn = OBJ_DESCR(*ocl);
|
|
register const char *un = ocl->oc_uname;
|
|
register 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:
|
|
Strcpy(buf, "spellbook");
|
|
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)
|
|
register 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;
|
|
}
|
|
|
|
|
|
/* 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. The kludge used is
|
|
* to temporarily set Blind so that xname() skips the dknown setting. This
|
|
* assumes that we don't want to do this too often; if this function becomes
|
|
* frequently used, it'd probably be better to pass a parameter to xname()
|
|
* or doname() instead.
|
|
*/
|
|
char *
|
|
distant_name(obj, func)
|
|
register struct obj *obj;
|
|
char *FDECL((*func), (OBJ_P));
|
|
{
|
|
char *str;
|
|
|
|
long save_Blinded = Blinded;
|
|
Blinded = 1;
|
|
str = (*func)(obj);
|
|
Blinded = save_Blinded;
|
|
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;
|
|
}
|
|
|
|
char *
|
|
xname(obj)
|
|
register struct obj *obj;
|
|
{
|
|
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);
|
|
const char *un = ocl->oc_uname;
|
|
boolean pluralize = (obj->quan != 1L);
|
|
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) 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 ");
|
|
|
|
if (!dknown)
|
|
Strcat(buf, dn ? dn : actualn);
|
|
else if (nn)
|
|
Strcat(buf, actualn);
|
|
else if (un) {
|
|
Strcat(buf, dn ? dn : actualn);
|
|
Strcat(buf, " called ");
|
|
Strcat(buf, un);
|
|
} else
|
|
Strcat(buf, dn ? dn : actualn);
|
|
/* 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);
|
|
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) {
|
|
register struct fruit *f;
|
|
|
|
for(f=ffruit; f; f = f->nextf) {
|
|
if(f->fid == obj->spe) {
|
|
Strcpy(buf, f->fname);
|
|
break;
|
|
}
|
|
}
|
|
if (!f) {
|
|
impossible("Bad fruit #%d?", obj->spe);
|
|
Strcpy(buf, "fruit");
|
|
} else 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;
|
|
}
|
|
|
|
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 (!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 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 */
|
|
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) {
|
|
/* copy xname's result so that we can reuse its return buffer */
|
|
Strcpy(tmpbuf, onm);
|
|
/* "the Nth arrow"; value will eventually be passed to an() or
|
|
The(), both of which correctly handle this "the " prefix */
|
|
Sprintf(onm, "the %d%s %s", m_shot.i, ordin(m_shot.i), tmpbuf);
|
|
}
|
|
|
|
return onm;
|
|
}
|
|
|
|
|
|
/* used for naming "the unique_item" instead of "a unique_item" */
|
|
boolean
|
|
the_unique_obj(obj)
|
|
register struct obj *obj;
|
|
{
|
|
boolean known = (obj->known || iflags.override_ID);
|
|
if (!obj->dknown && !iflags.override_ID)
|
|
return FALSE;
|
|
else if (obj->otyp == FAKE_AMULET_OF_YENDOR && !known)
|
|
return TRUE; /* lie */
|
|
else
|
|
return (boolean)(objects[obj->otyp].oc_unique &&
|
|
(known || obj->otyp == AMULET_OF_YENDOR));
|
|
}
|
|
|
|
/* should monster type be prefixed with "the"? (mostly used for corpses) */
|
|
boolean
|
|
the_unique_pm(ptr)
|
|
struct permonst *ptr;
|
|
{
|
|
boolean uniq;
|
|
|
|
/* even though monsters with personal names are unique, we want to
|
|
describe them as "Name" rather than "the Name" */
|
|
if (type_is_pname(ptr)) return FALSE;
|
|
|
|
uniq = (ptr->geno & G_UNIQ) ? TRUE : FALSE;
|
|
/* high priest is unique if it includes "of <deity>", otherwise not
|
|
(caller needs to handle the 1st possibility; we assume the 2nd);
|
|
worm tail should be irrelevant but is included for completeness */
|
|
if (ptr == &mons[PM_HIGH_PRIEST] || ptr == &mons[PM_LONG_WORM_TAIL])
|
|
uniq = FALSE;
|
|
/* Wizard no longer needs this; he's flagged as unique these days */
|
|
if (ptr == &mons[PM_WIZARD_OF_YENDOR])
|
|
uniq = TRUE;
|
|
return uniq;
|
|
}
|
|
|
|
STATIC_OVL void
|
|
add_erosion_words(obj,prefix)
|
|
struct obj *obj;
|
|
char *prefix;
|
|
{
|
|
boolean iscrys = (obj->otyp == CRYSKNIFE);
|
|
boolean rknown;
|
|
|
|
rknown = (iflags.override_ID == 0) ? obj->rknown : TRUE;
|
|
|
|
if (!is_damageable(obj) && !iscrys) return;
|
|
|
|
/* The only cases where any of these bits do double duty are for
|
|
* rotted food and diluted potions, which are all not is_damageable().
|
|
*/
|
|
if (obj->oeroded && !iscrys) {
|
|
switch (obj->oeroded) {
|
|
case 2: Strcat(prefix, "very "); break;
|
|
case 3: Strcat(prefix, "thoroughly "); break;
|
|
}
|
|
Strcat(prefix, is_rustprone(obj) ? "rusty " : "burnt ");
|
|
}
|
|
if (obj->oeroded2 && !iscrys) {
|
|
switch (obj->oeroded2) {
|
|
case 2: Strcat(prefix, "very "); break;
|
|
case 3: Strcat(prefix, "thoroughly "); break;
|
|
}
|
|
Strcat(prefix, is_corrodeable(obj) ? "corroded " :
|
|
"rotted ");
|
|
}
|
|
if (rknown && obj->oerodeproof)
|
|
Strcat(prefix,
|
|
iscrys ? "fixed " :
|
|
is_rustprone(obj) ? "rustproof " :
|
|
is_corrodeable(obj) ? "corrodeproof " : /* "stainless"? */
|
|
is_flammable(obj) ? "fireproof " : "");
|
|
}
|
|
|
|
char *
|
|
doname(obj)
|
|
register struct obj *obj;
|
|
{
|
|
boolean ispoisoned = FALSE;
|
|
boolean known, cknown, bknown, lknown;
|
|
int omndx = obj->corpsenm;
|
|
char prefix[PREFIX];
|
|
char tmpbuf[PREFIX+1];
|
|
/* 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 = cknown = bknown = lknown = TRUE;
|
|
} else {
|
|
known = obj->known;
|
|
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)
|
|
Sprintf(prefix, "%ld ", obj->quan);
|
|
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 ((!known || !objects[obj->otyp].oc_charged ||
|
|
(obj->oclass == ARMOR_CLASS ||
|
|
obj->oclass == RING_CLASS))
|
|
/* 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 unneccesary,
|
|
* 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.
|
|
*/
|
|
#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)
|
|
Strcat(prefix, "unlockable ");
|
|
else if (obj->olocked)
|
|
Strcat(prefix, "locked ");
|
|
else
|
|
Strcat(prefix, "unlocked ");
|
|
}
|
|
|
|
if (obj->greased) Strcat(prefix, "greased ");
|
|
|
|
if (cknown && Has_contents(obj)) {
|
|
/* we count all objects (obj->quantity); perhaps we should
|
|
count seperate stacks instead (or even introduce a user
|
|
preference option to choose between the two alternatives)
|
|
since it's somewhat odd so see "containing 1002 items"
|
|
when there are 2 scrolls plus 1000 gold pieces */
|
|
long itemcount = count_contents(obj, FALSE, TRUE, TRUE);
|
|
|
|
Sprintf(eos(bp), " containing %ld item%s",
|
|
itemcount, plur(itemcount));
|
|
}
|
|
|
|
switch(obj->oclass) {
|
|
case AMULET_CLASS:
|
|
if(obj->owornmask & W_AMUL)
|
|
Strcat(bp, " (being worn)");
|
|
break;
|
|
case WEAPON_CLASS:
|
|
if(ispoisoned)
|
|
Strcat(prefix, "poisoned ");
|
|
plus:
|
|
add_erosion_words(obj, prefix);
|
|
if(known) {
|
|
Strcat(prefix, sitoa(obj->spe));
|
|
Strcat(prefix, " ");
|
|
}
|
|
break;
|
|
case ARMOR_CLASS:
|
|
if(obj->owornmask & W_ARMOR)
|
|
Strcat(bp, (obj == uskin) ? " (embedded in your skin)" :
|
|
" (being worn)");
|
|
goto plus;
|
|
case TOOL_CLASS:
|
|
/* weptools already get this done when we go to the +n code */
|
|
if (!is_weptool(obj))
|
|
add_erosion_words(obj, prefix);
|
|
if(obj->owornmask & (W_TOOL /* blindfold */ | W_SADDLE)) {
|
|
Strcat(bp, " (being worn)");
|
|
break;
|
|
}
|
|
if (obj->otyp == LEASH && obj->leashmon != 0) {
|
|
Strcat(bp, " (in use)");
|
|
break;
|
|
}
|
|
if (is_weptool(obj))
|
|
goto plus;
|
|
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:
|
|
add_erosion_words(obj, prefix);
|
|
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:
|
|
add_erosion_words(obj, prefix);
|
|
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) {
|
|
Sprintf(prefix, "%s ",
|
|
corpse_xname(obj, prefix,
|
|
CXN_ARTICLE|CXN_NOCORPSE));
|
|
} 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(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));
|
|
}
|
|
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);
|
|
}
|
|
bp = strprepend(bp, prefix);
|
|
return(bp);
|
|
}
|
|
|
|
/* used from invent.c */
|
|
boolean
|
|
not_fully_identified(otmp)
|
|
register 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) && /* (redunant) */
|
|
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));
|
|
}
|
|
|
|
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;
|
|
const char *mname;
|
|
|
|
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 (!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);
|
|
}
|
|
|
|
/* treat an object as fully ID'd when it might be used as reason for death */
|
|
char *
|
|
killer_xname(obj)
|
|
struct obj *obj;
|
|
{
|
|
struct obj save_obj;
|
|
unsigned save_ocknown;
|
|
char *buf, *save_ocuname, *save_oname = (char *)0;
|
|
|
|
/* bypass object twiddling for artifacts */
|
|
if (obj->oartifact) return bare_artifactname(obj);
|
|
|
|
/* remember original settings for core of the object;
|
|
oextra structs other than oname don't matter here--since they
|
|
aren't modified they don't need to be saved and restored */
|
|
save_obj = *obj;
|
|
if (has_oname(obj)) save_oname = ONAME(obj);
|
|
|
|
/* killer name should be more specific than general xname; however, exact
|
|
info like blessed/cursed and rustproof makes things be too verbose */
|
|
obj->known = obj->dknown = 1;
|
|
obj->bknown = obj->rknown = obj->greased = 0;
|
|
/* if character is a priest[ess], bknown will get toggled back on */
|
|
if (obj->otyp != POT_WATER) obj->blessed = obj->cursed = 0;
|
|
else obj->bknown = 1; /* describe holy/unholy water as such */
|
|
/* "killed by poisoned <obj>" would be misleading when poison is
|
|
not the cause of death and "poisoned by poisoned <obj>" would
|
|
be redundant when it is, so suppress "poisoned" prefix */
|
|
obj->opoisoned = 0;
|
|
/* strip user-supplied name; artifacts keep theirs */
|
|
if (!obj->oartifact && save_oname) ONAME(obj) = (char *)0;
|
|
/* temporarily identify the type of object */
|
|
save_ocknown = objects[obj->otyp].oc_name_known;
|
|
objects[obj->otyp].oc_name_known = 1;
|
|
save_ocuname = objects[obj->otyp].oc_uname;
|
|
objects[obj->otyp].oc_uname = 0; /* avoid "foo called bar" */
|
|
|
|
/* format the object */
|
|
if (obj->otyp == CORPSE) {
|
|
buf = nextobuf();
|
|
Strcpy(buf, corpse_xname(obj, (const char *)0, CXN_NORMAL));
|
|
} else if (obj->otyp == SLIME_MOLD) {
|
|
/* concession to "most unique deaths competition" in the annual
|
|
devnull tournament, suppress player supplied fruit names because
|
|
those can be used to fake other objects and dungeon features */
|
|
buf = nextobuf();
|
|
Sprintf(buf, "deadly slime mold%s", plur(obj->quan));
|
|
} else {
|
|
buf = xname(obj);
|
|
}
|
|
/* apply an article if appropriate; caller should always use KILLED_BY */
|
|
if (obj->quan == 1L && !strstri(buf, "'s ") && !strstri(buf, "s' "))
|
|
buf = (obj_is_pname(obj) || the_unique_obj(obj)) ? the(buf) : an(buf);
|
|
|
|
objects[obj->otyp].oc_name_known = save_ocknown;
|
|
objects[obj->otyp].oc_uname = save_ocuname;
|
|
*obj = save_obj; /* restore object's core settings */
|
|
if (!obj->oartifact && save_oname) ONAME(obj) = save_oname;
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* xname,doname,&c with long results reformatted to omit some stuff */
|
|
char *
|
|
short_oname(obj, func, altfunc, lenlimit)
|
|
struct obj *obj;
|
|
char *FDECL((*func), (OBJ_P)), /* main formatting routine */
|
|
*FDECL((*altfunc), (OBJ_P)); /* alternate for shortest result */
|
|
unsigned lenlimit;
|
|
{
|
|
struct obj save_obj;
|
|
char unamebuf[12], onamebuf[12],
|
|
*save_oname, *save_uname, *outbuf;
|
|
|
|
outbuf = (*func)(obj);
|
|
if ((unsigned)strlen(outbuf) <= lenlimit) return outbuf;
|
|
|
|
/* shorten called string to fairly small amount */
|
|
save_uname = objects[obj->otyp].oc_uname;
|
|
if (save_uname && strlen(save_uname) >= sizeof unamebuf) {
|
|
(void) strncpy(unamebuf, save_uname, sizeof unamebuf - 4);
|
|
Strcpy(unamebuf + sizeof unamebuf - 4, "...");
|
|
objects[obj->otyp].oc_uname = unamebuf;
|
|
releaseobuf(outbuf);
|
|
outbuf = (*func)(obj);
|
|
objects[obj->otyp].oc_uname = save_uname; /* restore called string */
|
|
if ((unsigned)strlen(outbuf) <= lenlimit) return outbuf;
|
|
}
|
|
|
|
/* shorten named string to fairly small amount */
|
|
save_oname = has_oname(obj) ? ONAME(obj) : 0;
|
|
if (save_oname && strlen(save_oname) >= sizeof onamebuf) {
|
|
(void) strncpy(onamebuf, save_oname, sizeof onamebuf - 4);
|
|
Strcpy(onamebuf + sizeof onamebuf - 4, "...");
|
|
ONAME(obj) = onamebuf;
|
|
releaseobuf(outbuf);
|
|
outbuf = (*func)(obj);
|
|
ONAME(obj) = save_oname; /* restore named string */
|
|
if ((unsigned)strlen(outbuf) <= lenlimit) return outbuf;
|
|
}
|
|
|
|
/* shorten both called and named strings;
|
|
unamebuf and onamebuf have both already been populated */
|
|
if (save_uname && strlen(save_uname) >= sizeof unamebuf &&
|
|
save_oname && strlen(save_oname) >= sizeof onamebuf) {
|
|
objects[obj->otyp].oc_uname = unamebuf;
|
|
ONAME(obj) = onamebuf;
|
|
releaseobuf(outbuf);
|
|
outbuf = (*func)(obj);
|
|
if ((unsigned)strlen(outbuf) <= lenlimit) {
|
|
objects[obj->otyp].oc_uname = save_uname;
|
|
ONAME(obj) = save_oname;
|
|
return outbuf;
|
|
}
|
|
}
|
|
|
|
/* still long; strip several name-lengthening attributes;
|
|
called and named strings are still in truncated form */
|
|
save_obj = *obj;
|
|
obj->bknown = obj->rknown = obj->greased = 0;
|
|
obj->oeroded = obj->oeroded2 = 0;
|
|
releaseobuf(outbuf);
|
|
outbuf = (*func)(obj);
|
|
if (altfunc && (unsigned)strlen(outbuf) > lenlimit) {
|
|
/* still long; use the alternate function (usually one of
|
|
the jackets around minimal_xname()) */
|
|
releaseobuf(outbuf);
|
|
outbuf = (*altfunc)(obj);
|
|
}
|
|
/* restore the object */
|
|
*obj = save_obj;
|
|
if (save_oname) ONAME(obj) = save_oname;
|
|
if (save_uname) objects[obj->otyp].oc_uname = save_uname;
|
|
|
|
/* use whatever we've got, whether it's too long or not */
|
|
return outbuf;
|
|
}
|
|
|
|
/*
|
|
* Used if only one of a collection of objects is named (e.g. in eat.c).
|
|
*/
|
|
const char *
|
|
singular(otmp, func)
|
|
register struct obj *otmp;
|
|
char *FDECL((*func), (OBJ_P));
|
|
{
|
|
long savequan;
|
|
char *nam;
|
|
|
|
/* using xname for corpses does not give the monster type */
|
|
if (otmp->otyp == CORPSE && func == xname) func = cxname;
|
|
|
|
savequan = otmp->quan;
|
|
otmp->quan = 1L;
|
|
nam = (*func)(otmp);
|
|
otmp->quan = savequan;
|
|
return nam;
|
|
}
|
|
|
|
char *
|
|
an(str)
|
|
register const char *str;
|
|
{
|
|
char *buf = nextobuf();
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (strncmpi(str, "the ", 4) &&
|
|
strcmp(str, "molten lava") &&
|
|
strcmp(str, "iron bars") &&
|
|
strcmp(str, "ice")) {
|
|
if (index(vowels, *str) &&
|
|
strncmp(str, "one-", 4) &&
|
|
strncmp(str, "useful", 6) &&
|
|
strncmp(str, "unicorn", 7) &&
|
|
strncmp(str, "uranium", 7) &&
|
|
strncmp(str, "eucalyptus", 10))
|
|
Strcpy(buf, "an ");
|
|
else
|
|
Strcpy(buf, "a ");
|
|
}
|
|
|
|
Strcat(buf, str);
|
|
return buf;
|
|
}
|
|
|
|
char *
|
|
An(str)
|
|
const char *str;
|
|
{
|
|
register 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') {
|
|
/* 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, ' ')) || (tmp = rindex(str, '-'))) &&
|
|
(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;
|
|
{
|
|
register char *tmp = the(str);
|
|
*tmp = highc(*tmp);
|
|
return tmp;
|
|
}
|
|
|
|
/* returns "count cxname(otmp)" or just cxname(otmp) if count == 1 */
|
|
char *
|
|
aobjnam(otmp,verb)
|
|
register struct obj *otmp;
|
|
register const char *verb;
|
|
{
|
|
register char *bp = cxname(otmp);
|
|
char prefix[PREFIX];
|
|
|
|
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)
|
|
register struct obj *otmp;
|
|
register 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)
|
|
register struct obj *obj;
|
|
{
|
|
register 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)
|
|
register struct obj *otmp;
|
|
register const char *verb;
|
|
{
|
|
char *buf;
|
|
|
|
/*
|
|
* verb is given in plural (without trailing s). Return as input
|
|
* if the result of xname(otmp) would be plural. Don't bother
|
|
* recomputing xname(otmp) at this time.
|
|
*/
|
|
if (!is_plural(otmp))
|
|
return vtense((char *)0, verb);
|
|
|
|
buf = nextobuf();
|
|
Strcpy(buf, verb);
|
|
return buf;
|
|
}
|
|
|
|
/* various singular words that vtense would otherwise categorize as plural;
|
|
also used by makesingular() to catch some special cases */
|
|
static const char * const special_subjs[] = {
|
|
"erinys",
|
|
"manes", /* this one is ambiguous */
|
|
"Cyclops",
|
|
"Hippocrates",
|
|
"Pelias",
|
|
"aklys",
|
|
"amnesia",
|
|
"detect monsters",
|
|
"paralysis",
|
|
"shape changers",
|
|
"nemesis",
|
|
0
|
|
/* note: "detect monsters" and "shape changers" are normally
|
|
caught via "<something>(s) of <whatever>", but they can be
|
|
wished for using the shorter form, so we include them here
|
|
to accomodate usage by makesingular during wishing */
|
|
};
|
|
|
|
/* return form of the verb (input plural) for present tense 3rd person subj */
|
|
char *
|
|
vtense(subj, verb)
|
|
register const char *subj;
|
|
register const char *verb;
|
|
{
|
|
char *buf = nextobuf(), *bspot;
|
|
int len, ltmp;
|
|
const char *sp, *spot;
|
|
const char * const *spec;
|
|
|
|
/*
|
|
* verb is given in plural (without trailing s). Return as input
|
|
* if subj appears to be plural. Add special cases as necessary.
|
|
* Many hard cases can already be handled by using otense() instead.
|
|
* If this gets much bigger, consider decomposing makeplural.
|
|
* Note: monster names are not expected here (except before corpse).
|
|
*
|
|
* Special case: allow null sobj to get the singular 3rd person
|
|
* present tense form so we don't duplicate this code elsewhere.
|
|
*/
|
|
if (subj) {
|
|
if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3))
|
|
goto sing;
|
|
spot = (const char *)0;
|
|
for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) {
|
|
if (!strncmpi(sp, " of ", 4) ||
|
|
!strncmpi(sp, " from ", 6) ||
|
|
!strncmpi(sp, " called ", 8) ||
|
|
!strncmpi(sp, " named ", 7) ||
|
|
!strncmpi(sp, " labeled ", 9)) {
|
|
if (sp != subj) spot = sp - 1;
|
|
break;
|
|
}
|
|
}
|
|
len = (int) strlen(subj);
|
|
if (!spot) spot = subj + len - 1;
|
|
|
|
/*
|
|
* plural: anything that ends in 's', but not '*us' or '*ss'.
|
|
* Guess at a few other special cases that makeplural creates.
|
|
*/
|
|
if ((lowc(*spot) == 's' && spot != subj &&
|
|
!index("us", lowc(*(spot-1)))) ||
|
|
!BSTRNCMPI(subj, spot-3, "eeth", 4) ||
|
|
!BSTRNCMPI(subj, spot-3, "feet", 4) ||
|
|
!BSTRNCMPI(subj, spot-1, "ia", 2) ||
|
|
!BSTRNCMPI(subj, spot-1, "ae", 2)) {
|
|
/* check for special cases to avoid false matches */
|
|
len = (int)(spot - subj) + 1;
|
|
for (spec = special_subjs; *spec; spec++) {
|
|
ltmp = strlen(*spec);
|
|
if (len == ltmp && !strncmpi(*spec, subj, len)) goto sing;
|
|
/* also check for <prefix><space><special_subj>
|
|
to catch things like "the invisible erinys" */
|
|
if (len > ltmp && *(spot - ltmp) == ' ' &&
|
|
!strncmpi(*spec, spot - ltmp + 1, ltmp)) goto sing;
|
|
}
|
|
|
|
return strcpy(buf, verb);
|
|
}
|
|
/*
|
|
* 3rd person plural doesn't end in telltale 's';
|
|
* 2nd person singular behaves as if plural.
|
|
*/
|
|
if (!strcmpi(subj, "they") || !strcmpi(subj, "you"))
|
|
return strcpy(buf, verb);
|
|
}
|
|
|
|
sing:
|
|
Strcpy(buf, verb);
|
|
len = (int)strlen(buf);
|
|
bspot = buf + len - 1;
|
|
|
|
if (!strcmpi(buf, "are")) {
|
|
Strcasecpy(buf, "is");
|
|
} else if (!strcmpi(buf, "have")) {
|
|
Strcasecpy(bspot-1, "s");
|
|
} else if (index("zxs", lowc(*bspot)) ||
|
|
(len >= 2 && lowc(*bspot) == 'h' &&
|
|
index("cs", lowc(*(bspot-1)))) ||
|
|
(len == 2 && lowc(*bspot) == 'o')) {
|
|
/* Ends in z, x, s, ch, sh; add an "es" */
|
|
Strcasecpy(bspot+1, "es");
|
|
} else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot-1)))) {
|
|
/* like "y" case in makeplural */
|
|
Strcasecpy(bspot, "ies");
|
|
} else {
|
|
Strcasecpy(bspot+1, "s");
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
struct sing_plur {
|
|
const char *sing, *plur;
|
|
};
|
|
|
|
/* word pairs that don't fit into formula-based transformations;
|
|
also some suffices which have very few--often one--matches or
|
|
which aren't systematically reversible (knives, staves) */
|
|
static struct sing_plur one_off[] = {
|
|
{ "child", "children" }, /* (for wise guys who give their food funny names) */
|
|
{ "cubus", "cubi" }, /* in-/suc-cubus */
|
|
{ "culus", "culi" }, /* homunculus */
|
|
{ "djinni", "djinn" },
|
|
{ "erinys", "erinyes" },
|
|
{ "foot", "feet" },
|
|
{ "fungus", "fungi" },
|
|
{ "knife", "knives" },
|
|
{ "labrum", "labra" }, /* candelabrum */
|
|
{ "louse", "lice" },
|
|
{ "mouse", "mice" },
|
|
{ "mumak", "mumakil" },
|
|
{ "nemesis", "nemeses" },
|
|
{ "rtex", "rtices" }, /* vortex */
|
|
{ "tooth", "teeth" },
|
|
{ "staff", "staves" },
|
|
{ 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 */
|
|
"deer", "fish", "tuna", "yaki", "-hai",
|
|
"krill", "manes", "ninja", "sheep", "ronin", "roshi", "shito", "tengu",
|
|
"ki-rin", "Nazgul",
|
|
"gunyoki", "piranha", "samurai",
|
|
"shuriken",
|
|
0,
|
|
/* Note: "fish" and "piranha" are collective plurals, suitable
|
|
for "wiped out all <foo>". For "3 <foo>", they should be
|
|
"fishes" and "piranhas" instead. We settle for collective
|
|
variant instead of attempting to support both. */
|
|
};
|
|
|
|
/* singularize/pluralize decisiions 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;
|
|
}
|
|
}
|
|
|
|
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; chiefly used for user-defined fruits. 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.5.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 */
|
|
"men", /* also catches women, watchmen */
|
|
"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 */
|
|
(len < 6 || strcmpi(spot-5, "shaman")) &&
|
|
(len < 5 || strcmpi(spot-4, "human"))) {
|
|
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.5.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")) {
|
|
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;
|
|
}
|
|
|
|
/* compare user string against object name string using fuzzy matching */
|
|
STATIC_OVL boolean
|
|
wishymatch(u_str, o_str, retry_inverted)
|
|
const char *u_str; /* from user, so might be variant spelling */
|
|
const char *o_str; /* from objects[], so is in canonical form */
|
|
boolean retry_inverted; /* optional extra "of" handling */
|
|
{
|
|
static NEARDATA const char
|
|
detect_SP[] = "detect ", SP_detection[] = " detection";
|
|
char *p, buf[BUFSZ];
|
|
|
|
/* ignore spaces & hyphens and upper/lower case when comparing */
|
|
if (fuzzymatch(u_str, o_str, " -", TRUE)) return TRUE;
|
|
|
|
if (retry_inverted) {
|
|
const char *u_of, *o_of;
|
|
|
|
/* when just one of the strings is in the form "foo of bar",
|
|
convert it into "bar foo" and perform another comparison */
|
|
u_of = strstri(u_str, " of ");
|
|
o_of = strstri(o_str, " of ");
|
|
if (u_of && !o_of) {
|
|
Strcpy(buf, u_of + 4);
|
|
p = eos(strcat(buf, " "));
|
|
while (u_str < u_of) *p++ = *u_str++;
|
|
*p = '\0';
|
|
return fuzzymatch(buf, o_str, " -", TRUE);
|
|
} else if (o_of && !u_of) {
|
|
Strcpy(buf, o_of + 4);
|
|
p = eos(strcat(buf, " "));
|
|
while (o_str < o_of) *p++ = *o_str++;
|
|
*p = '\0';
|
|
return fuzzymatch(u_str, buf, " -", TRUE);
|
|
}
|
|
}
|
|
|
|
/* [note: if something like "elven speed boots" ever gets added, these
|
|
special cases should be changed to call wishymatch() recursively in
|
|
order to get the "of" inversion handling] */
|
|
if (!strncmp(o_str, "dwarvish ", 9)) {
|
|
if (!strncmpi(u_str, "dwarven ", 8))
|
|
return fuzzymatch(u_str + 8, o_str + 9, " -", TRUE);
|
|
} else if (!strncmp(o_str, "elven ", 6)) {
|
|
if (!strncmpi(u_str, "elvish ", 7))
|
|
return fuzzymatch(u_str + 7, o_str + 6, " -", TRUE);
|
|
else if (!strncmpi(u_str, "elfin ", 6))
|
|
return fuzzymatch(u_str + 6, o_str + 6, " -", TRUE);
|
|
} else if (!strncmp(o_str, detect_SP, sizeof detect_SP - 1)) {
|
|
/* check for "detect <foo>" vs "<foo> detection" */
|
|
if ((p = strstri(u_str, SP_detection)) != 0 &&
|
|
!*(p + sizeof SP_detection - 1)) {
|
|
/* convert "<foo> detection" into "detect <foo>" */
|
|
*p = '\0';
|
|
Strcat(strcpy(buf, detect_SP), u_str);
|
|
/* "detect monster" -> "detect monsters" */
|
|
if (!strcmpi(u_str, "monster")) Strcat(buf, "s");
|
|
*p = ' ';
|
|
return fuzzymatch(buf, o_str, " -", TRUE);
|
|
}
|
|
} else if (strstri(o_str, SP_detection)) {
|
|
/* and the inverse, "<foo> detection" vs "detect <foo>" */
|
|
if (!strncmpi(u_str, detect_SP, sizeof detect_SP - 1)) {
|
|
/* convert "detect <foo>s" into "<foo> detection" */
|
|
p = makesingular(u_str + sizeof detect_SP - 1);
|
|
Strcat(strcpy(buf, p), SP_detection);
|
|
/* caller may be looping through objects[], so avoid
|
|
churning through all the obufs */
|
|
releaseobuf(p);
|
|
return fuzzymatch(buf, o_str, " -", TRUE);
|
|
}
|
|
} else if (strstri(o_str, "ability")) {
|
|
/* when presented with "foo of bar", make singular() 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 },
|
|
{ "hook", GRAPPLING_HOOK },
|
|
{ "grappling iron", GRAPPLING_HOOK },
|
|
{ "grapnel", GRAPPLING_HOOK },
|
|
{ "grapple", GRAPPLING_HOOK },
|
|
{ (const char *)0, 0 },
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
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;
|
|
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;
|
|
|
|
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, "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 break;
|
|
bp += l;
|
|
}
|
|
if(!cnt) cnt = 1; /* %% what with "gems" etc. ? */
|
|
if (strlen(bp) > 1) {
|
|
if ((p = rindex(bp, '(')) != 0) {
|
|
if (p > bp && p[-1] == ' ') p[-1] = 0;
|
|
else *p = 0;
|
|
p++;
|
|
if (!strcmpi(p, "lit)")) {
|
|
islit = 1;
|
|
} 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;
|
|
} else {
|
|
spesgn = 1;
|
|
p++;
|
|
if (*p) Strcat(bp, 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
|
|
refered 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 refered 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(cnt > 1 && !strncmpi(bp, "pairs of ",9)) {
|
|
bp += 9;
|
|
cnt *= 2;
|
|
} else if (!strncmpi(bp, "set of ",7)) {
|
|
bp += 7;
|
|
} else if (!strncmpi(bp, "sets of ",8)) {
|
|
bp += 8;
|
|
}
|
|
|
|
/*
|
|
* 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) &&
|
|
(tmp = tin_variety_txt(p+7, &tinv)) &&
|
|
(mntmp = name_to_mon(p+7+tmp)) >= LOW_PM) {
|
|
*(p+3) = 0;
|
|
tvariety = tinv;
|
|
} 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! */
|
|
if (strncmpi(bp, "wizard lock", 11)) /* not the "wizard" monster! */
|
|
if (strncmpi(bp, "ninja-to", 8)) /* not the "ninja" rank */
|
|
if (strncmpi(bp, "master key", 10)) /* not the "master" rank */
|
|
if (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 remainer beyond "u" */
|
|
p += 4;
|
|
while ((*p = *(p + 1)) != '\0') ++p; /* self terminating */
|
|
}
|
|
}
|
|
|
|
/* dragon scales - assumes order of dragons */
|
|
if(!strcmpi(bp, "scales") &&
|
|
mntmp >= PM_GRAY_DRAGON && mntmp <= PM_YELLOW_DRAGON) {
|
|
typ = GRAY_DRAGON_SCALES + mntmp - PM_GRAY_DRAGON;
|
|
mntmp = NON_PM; /* no monster */
|
|
goto typfnd;
|
|
}
|
|
|
|
p = eos(bp);
|
|
if(!BSTRCMPI(bp, p-10, "holy water")) {
|
|
typ = POT_WATER;
|
|
if ((p-bp) >= 12 && *(p-12) == 'u')
|
|
iscursed = 1; /* unholy water */
|
|
else blessed = 1;
|
|
goto typfnd;
|
|
}
|
|
if(unlabeled && !BSTRCMPI(bp, p-6, "scroll")) {
|
|
typ = SCR_BLANK_PAPER;
|
|
goto typfnd;
|
|
}
|
|
if(unlabeled && !BSTRCMPI(bp, p-9, "spellbook")) {
|
|
typ = SPE_BLANK_PAPER;
|
|
goto typfnd;
|
|
}
|
|
/*
|
|
* NOTE: Gold pieces are handled as objects nowadays, and therefore
|
|
* this section should probably be reconsidered as well as the entire
|
|
* gold/money concept. Maybe we want to add other monetary units as
|
|
* well in the future. (TH)
|
|
*/
|
|
if(!BSTRCMPI(bp, p-10, "gold piece") || !BSTRCMPI(bp, p-7, "zorkmid") ||
|
|
!strcmpi(bp, "gold") || !strcmpi(bp, "money") ||
|
|
!strcmpi(bp, "coin") || *bp == GOLD_SYM) {
|
|
if (cnt > 5000 && !wizard) cnt = 5000;
|
|
else if (cnt < 1) cnt = 1;
|
|
otmp = mksobj(GOLD_PIECE, FALSE, FALSE);
|
|
otmp->quan = (long) cnt;
|
|
otmp->owt = weight(otmp);
|
|
context.botl = 1;
|
|
return otmp;
|
|
}
|
|
|
|
/* check for single character object class code ("/" for wand, &c) */
|
|
if (strlen(bp) == 1 &&
|
|
(i = def_char_to_objclass(*bp)) < MAXOCLASSES &&
|
|
i > ILLOBJ_CLASS && (i != VENOM_CLASS || wizard)) {
|
|
oclass = i;
|
|
goto any;
|
|
}
|
|
|
|
/* Search for class names: XXXXX potion, scroll of XXXXX. Avoid */
|
|
/* false hits on, e.g., rings for "ring mail". */
|
|
if(strncmpi(bp, "enchant ", 8) &&
|
|
strncmpi(bp, "destroy ", 8) &&
|
|
strncmpi(bp, "detect food", 11) &&
|
|
strncmpi(bp, "food detection", 14) &&
|
|
strncmpi(bp, "ring mail", 9) &&
|
|
strncmpi(bp, "studded leather armor", 21) &&
|
|
strncmpi(bp, "leather armor", 13) &&
|
|
strncmpi(bp, "tooled horn", 11) &&
|
|
strncmpi(bp, "food ration", 11) &&
|
|
strncmpi(bp, "meat ring", 9)
|
|
)
|
|
for (i = 0; i < (int)(sizeof wrpsym); i++) {
|
|
register int j = strlen(wrp[i]);
|
|
if(!strncmpi(bp, wrp[i], j)){
|
|
oclass = wrpsym[i];
|
|
if(oclass != AMULET_CLASS) {
|
|
bp += j;
|
|
if(!strncmpi(bp, " of ", 4)) actualn = bp+4;
|
|
/* else if(*bp) ?? */
|
|
} else
|
|
actualn = bp;
|
|
goto srch;
|
|
}
|
|
if(!BSTRCMPI(bp, p-j, wrp[i])){
|
|
oclass = wrpsym[i];
|
|
p -= j;
|
|
*p = 0;
|
|
if(p > bp && p[-1] == ' ') p[-1] = 0;
|
|
actualn = dn = bp;
|
|
goto srch;
|
|
}
|
|
}
|
|
|
|
/* Wishing in wizard mode can create traps and furniture.
|
|
* Part I: distinguish between trap and object for the two
|
|
* types of traps which have corresponding objects: bear trap
|
|
* and land mine. "beartrap" (object) and "bear trap" (trap)
|
|
* have a difference in spelling which we used to exploit by
|
|
* adding a special case in wishymatch(), but "land mine" is
|
|
* spelled the same either way so needs different handing.
|
|
* Since we need something else for land mine, we've dropped
|
|
* the bear trap hack so that both are handled exactly the
|
|
* same. To get an armed trap instead of a disarmed object,
|
|
* the player can prefix either the object name or the trap
|
|
* name with "trapped " (which ordinarily applies to chests
|
|
* and tins), or append something--anything at all except for
|
|
* " object", but " trap" is suggested--to either the trap
|
|
* name or the object name.
|
|
*/
|
|
if (wizard && (!strncmpi(bp, "bear", 4) || !strncmpi(bp, "land", 4))) {
|
|
boolean beartrap = (lowc(*bp) == 'b');
|
|
char *zp = bp + 4; /* skip "bear"/"land" */
|
|
|
|
if (*zp == ' ') ++zp; /* embedded space is optional */
|
|
if (!strncmpi(zp, beartrap ? "trap" : "mine", 4)) {
|
|
zp += 4;
|
|
if (trapped == 2 || !strcmpi(zp, " object")) {
|
|
/* "untrapped <foo>" or "<foo> object" */
|
|
typ = beartrap ? BEARTRAP : LAND_MINE;
|
|
goto typfnd;
|
|
} else if (trapped == 1 || *zp != '\0') {
|
|
/* "trapped <foo>" or "<foo> trap" (actually "<foo>*") */
|
|
int idx = trap_to_defsym(beartrap ? BEAR_TRAP : LANDMINE);
|
|
|
|
/* use canonical trap spelling, skip object matching */
|
|
Strcpy(bp, defsyms[idx].explanation);
|
|
goto wiztrap;
|
|
}
|
|
/* [no prefix or suffix; we're going to end up matching
|
|
the object name and getting a disarmed trap object] */
|
|
}
|
|
}
|
|
|
|
retry:
|
|
/* "grey stone" check must be before general "stone" */
|
|
for (i = 0; i < SIZE(o_ranges); i++)
|
|
if(!strcmpi(bp, o_ranges[i].name)) {
|
|
typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range);
|
|
goto typfnd;
|
|
}
|
|
|
|
if (!BSTRCMPI(bp, p-6, " stone")) {
|
|
p[-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])) && !strcmpi(actualn, zn)) {
|
|
typ = i;
|
|
goto typfnd;
|
|
}
|
|
}
|
|
}
|
|
i = oclass ? bases[(int)oclass] : 1;
|
|
while(i < NUM_OBJECTS && (!oclass || objects[i].oc_class == oclass)){
|
|
register const char *zn;
|
|
|
|
if (actualn && (zn = OBJ_NAME(objects[i])) != 0 &&
|
|
wishymatch(actualn, zn, TRUE)) {
|
|
typ = i;
|
|
goto typfnd;
|
|
}
|
|
if (dn && (zn = OBJ_DESCR(objects[i])) != 0 &&
|
|
wishymatch(dn, zn, FALSE)) {
|
|
/* don't match extra descriptions (w/o real name) */
|
|
if (!OBJ_NAME(objects[i])) return (struct obj *)0;
|
|
typ = i;
|
|
goto typfnd;
|
|
}
|
|
if (un && (zn = objects[i].oc_uname) != 0 &&
|
|
wishymatch(un, zn, FALSE)) {
|
|
typ = i;
|
|
goto typfnd;
|
|
}
|
|
i++;
|
|
}
|
|
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) 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 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: 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) &&
|
|
!(mvitals[mntmp].mvflags & G_NOCORPSE) &&
|
|
mons[mntmp].cnutrit != 0) {
|
|
otmp->corpsenm = mntmp;
|
|
}
|
|
break;
|
|
case CORPSE:
|
|
if (!(mons[mntmp].geno & G_UNIQ) &&
|
|
!(mvitals[mntmp].mvflags & G_NOCORPSE)) {
|
|
if (mons[mntmp].msound == MS_GUARDIAN)
|
|
mntmp = genus(mntmp,1);
|
|
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 EGG:
|
|
mntmp = can_be_hatched(mntmp);
|
|
/* this also sets hatch timer if appropriate */
|
|
set_corpsenm(otmp, 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 */
|
|
if (is_damageable(otmp) || otmp->otyp == CRYSKNIFE) {
|
|
if (eroded && (is_flammable(otmp) || is_rustprone(otmp)))
|
|
otmp->oeroded = eroded;
|
|
if (eroded2 && (is_corrodeable(otmp) || is_rottable(otmp)))
|
|
otmp->oeroded2 = eroded2;
|
|
|
|
/* set erodeproof */
|
|
if (erodeproof && !eroded && !eroded2)
|
|
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;
|
|
|
|
otmp = oname(otmp, name);
|
|
if (otmp->oartifact) {
|
|
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 += 160;
|
|
|
|
return(otmp);
|
|
}
|
|
|
|
int
|
|
rnd_class(first,last)
|
|
int first,last;
|
|
{
|
|
int i, x, sum=0;
|
|
|
|
if (first == last)
|
|
return (first);
|
|
for(i=first; i<=last; i++)
|
|
sum += objects[i].oc_prob;
|
|
if (!sum) /* all zero */
|
|
return first + rn2(last-first+1);
|
|
x = rnd(sum);
|
|
for(i=first; i<=last; i++)
|
|
if (objects[i].oc_prob && (x -= objects[i].oc_prob) <= 0)
|
|
return i;
|
|
return 0;
|
|
}
|
|
|
|
STATIC_OVL const char *
|
|
Japanese_item_name(i)
|
|
int i;
|
|
{
|
|
struct Jitem *j = Japanese_items;
|
|
|
|
while(j->item) {
|
|
if (i == j->item)
|
|
return j->name;
|
|
j++;
|
|
}
|
|
return (const char *)0;
|
|
}
|
|
|
|
const char *
|
|
suit_simple_name(suit)
|
|
struct obj *suit;
|
|
{
|
|
const char *suitnm, *esuitp;
|
|
|
|
if (Is_dragon_mail(suit))
|
|
return "dragon mail"; /* <color> dragon scale mail */
|
|
else if (Is_dragon_scales(suit))
|
|
return "dragon scales";
|
|
suitnm = OBJ_NAME(objects[suit->otyp]);
|
|
esuitp = eos((char *)suitnm);
|
|
if (strlen(suitnm) > 5 && !strcmp(esuitp - 5, " mail"))
|
|
return "mail"; /* most suits fall into this category */
|
|
else if (strlen(suitnm) > 7 && !strcmp(esuitp - 7, " jacket"))
|
|
return "jacket"; /* leather jacket */
|
|
/* suit is lame but armor is ambiguous and body armor is absurd */
|
|
return "suit";
|
|
}
|
|
|
|
const char *
|
|
cloak_simple_name(cloak)
|
|
struct obj *cloak;
|
|
{
|
|
if (cloak) {
|
|
switch (cloak->otyp) {
|
|
case ROBE:
|
|
return "robe";
|
|
case MUMMY_WRAPPING:
|
|
return "wrapping";
|
|
case ALCHEMY_SMOCK:
|
|
return (objects[cloak->otyp].oc_name_known &&
|
|
cloak->dknown) ? "smock" : "apron";
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return "cloak";
|
|
}
|
|
|
|
/* helm vs hat for messages */
|
|
const char *
|
|
helm_simple_name(helmet)
|
|
struct obj *helmet;
|
|
{
|
|
/*
|
|
* There is some wiggle room here; the result has been chosen
|
|
* for consistency with the "protected by hard helmet" messages
|
|
* given for various bonks on the head: headgear that provides
|
|
* such protection is a "helm", that which doesn't is a "hat".
|
|
*
|
|
* elven leather helm / leather hat -> hat
|
|
* dwarvish iron helm / hard hat -> helm
|
|
* The rest are completely straightforward:
|
|
* fedora, cornuthaum, dunce cap -> hat
|
|
* all other types of helmets -> helm
|
|
*/
|
|
return (helmet && !is_metallic(helmet)) ? "hat" : "helm";
|
|
}
|
|
|
|
const char *
|
|
mimic_obj_name(mtmp)
|
|
struct monst *mtmp;
|
|
{
|
|
if (mtmp->m_ap_type == M_AP_OBJECT && mtmp->mappearance != STRANGE_OBJECT) {
|
|
int idx = objects[mtmp->mappearance].oc_descr_idx;
|
|
if (mtmp->mappearance == GOLD_PIECE) return "gold";
|
|
return obj_descr[idx].oc_name;
|
|
}
|
|
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*/
|