5672 lines
208 KiB
C
5672 lines
208 KiB
C
/* NetHack 3.7 objnam.c $NHDT-Date: 1737528848 2025/01/21 22:54:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.444 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/*-Copyright (c) Robert Patrick Rankin, 2011. */
|
|
/* 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
|
|
|
|
struct _readobjnam_data {
|
|
struct obj *otmp;
|
|
char *bp;
|
|
char *origbp;
|
|
char oclass;
|
|
char *un, *dn, *actualn;
|
|
const char *name;
|
|
char *p;
|
|
int cnt, spe, spesgn, typ, very, rechrg;
|
|
int blessed, uncursed, iscursed, ispoisoned, isgreased;
|
|
int eroded, eroded2, erodeproof, locked, unlocked, broken, real, fake;
|
|
int halfeaten, mntmp, contents;
|
|
int islit, unlabeled, ishistoric, isdiluted, trapped;
|
|
int doorless, open, closed, looted;
|
|
int tmp, tinv, tvariety, mgend;
|
|
int wetness, gsize;
|
|
int ftype;
|
|
boolean zombify;
|
|
char globbuf[BUFSZ];
|
|
char fruitbuf[BUFSZ];
|
|
};
|
|
|
|
staticfn char *strprepend(char *, const char *) NONNULL NONNULLARG1;
|
|
staticfn char *nextobuf(void) NONNULL;
|
|
staticfn void releaseobuf(char *) NONNULLARG1;
|
|
staticfn void xcalled(char *, int, const char *, const char *);
|
|
staticfn char *xname_flags(struct obj *, unsigned);
|
|
staticfn char *minimal_xname(struct obj *);
|
|
staticfn void add_erosion_words(struct obj *, char *);
|
|
staticfn char *doname_base(struct obj *obj, unsigned);
|
|
staticfn boolean singplur_lookup(char *, char *, boolean,
|
|
const char *const *);
|
|
staticfn char *singplur_compound(char *);
|
|
staticfn boolean ch_ksound(const char *basestr);
|
|
staticfn boolean badman(const char *, boolean);
|
|
staticfn boolean wishymatch(const char *, const char *, boolean);
|
|
staticfn short rnd_otyp_by_wpnskill(schar);
|
|
staticfn short rnd_otyp_by_namedesc(const char *, char, int);
|
|
staticfn void set_wallprop_from_str(char *) NONNULLARG1;
|
|
staticfn struct obj *wizterrainwish(struct _readobjnam_data *);
|
|
staticfn void dbterrainmesg(const char *, coordxy, coordxy) NONNULLARG1;
|
|
staticfn void readobjnam_init(char *, struct _readobjnam_data *);
|
|
staticfn int readobjnam_preparse(struct _readobjnam_data *);
|
|
staticfn void readobjnam_parse_charges(struct _readobjnam_data *);
|
|
staticfn int readobjnam_postparse1(struct _readobjnam_data *);
|
|
staticfn int readobjnam_postparse2(struct _readobjnam_data *);
|
|
staticfn int readobjnam_postparse3(struct _readobjnam_data *);
|
|
|
|
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)
|
|
#define Strncat(dst, src, cnt) (void) strncat(dst, src, cnt)
|
|
|
|
/* Concat(): append text to base, adjusted by delta, with bounds checking
|
|
via a pair of behind-the-scenes variables; delta is either 0 for normal
|
|
concatenation or 1 to replace the final character with something */
|
|
#define Concat(base, delta, text) \
|
|
do { \
|
|
Strncat(base ## _eos - delta, text, base ## spaceleft + delta); \
|
|
ConcUpdate(base); \
|
|
} while (0)
|
|
#define ConcatF1(base, delta, fmt, arg1) \
|
|
do { \
|
|
Snprintf(base ## _eos - delta, base ## spaceleft + delta, \
|
|
fmt, arg1); \
|
|
ConcUpdate(base); \
|
|
} while (0)
|
|
#define ConcatF2(base, delta, fmt, arg1, arg2) \
|
|
do { \
|
|
Snprintf(base ## _eos - delta, base ## spaceleft + delta, \
|
|
fmt, arg1, arg2); \
|
|
ConcUpdate(base); \
|
|
} while (0)
|
|
#define ConcUpdate(base) \
|
|
base ## _eos = eos(base), \
|
|
/* convert signed ptrdiff_t to unsigned size_t */ \
|
|
base ## spaceleft = (size_t) (base ## _end - base ## _eos)
|
|
|
|
/* 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 const struct Jitem Japanese_items[] = {
|
|
{ SHORT_SWORD, "wakizashi" },
|
|
{ BROADSWORD, "ninja-to" },
|
|
{ FLAIL, "nunchaku" },
|
|
{ GLAIVE, "naginata" },
|
|
{ LOCK_PICK, "osaku" },
|
|
{ WOODEN_HARP, "koto" },
|
|
{ MAGIC_HARP, "magic koto" },
|
|
{ KNIFE, "shito" },
|
|
{ PLATE_MAIL, "tanko" },
|
|
{ HELMET, "kabuto" },
|
|
{ LEATHER_GLOVES, "yugake" },
|
|
{ FOOD_RATION, "gunyoki" },
|
|
{ POT_BOOZE, "sake" },
|
|
{ 0, "" }
|
|
};
|
|
|
|
staticfn char *
|
|
strprepend(char *s, const char *pref)
|
|
{
|
|
char star_s = *s;
|
|
int i = (int) strlen(pref);
|
|
|
|
if (i > PREFIX) {
|
|
impossible("PREFIX too short (for %d).", i);
|
|
return s;
|
|
}
|
|
copynchars(s - i, pref, i + 1);
|
|
*s = star_s;
|
|
return s - i;
|
|
}
|
|
|
|
/* manage a pool of BUFSZ buffers, so callers don't have to */
|
|
static char NEARDATA obufs[NUMOBUF][BUFSZ];
|
|
static int obufidx = 0;
|
|
|
|
staticfn char *
|
|
nextobuf(void)
|
|
{
|
|
obufidx = (obufidx + 1) % NUMOBUF;
|
|
return obufs[obufidx];
|
|
}
|
|
|
|
/* put the most recently allocated buffer back if possible */
|
|
staticfn void
|
|
releaseobuf(char *bufp)
|
|
{
|
|
/* caller may not know whether bufp is the most recently allocated
|
|
buffer; if it isn't, do nothing; note that because of the somewhat
|
|
obscure PREFIX handling for object name formatting by xname(),
|
|
the pointer our caller has and is passing to us might be into the
|
|
middle of an obuf rather than the address returned by nextobuf() */
|
|
if (bufp >= obufs[obufidx]
|
|
&& bufp < obufs[obufidx] + sizeof obufs[obufidx]) /* obufs[][BUFSZ] */
|
|
obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF;
|
|
}
|
|
|
|
/* used by display_pickinv (invent.c, main whole-inventory routine) to
|
|
release each successive doname() result in order to try to avoid
|
|
clobbering all the obufs when 'perm_invent' is enabled and updated
|
|
while one or more obufs have been allocated but not released yet */
|
|
void
|
|
maybereleaseobuf(char *obuffer)
|
|
{
|
|
releaseobuf(obuffer);
|
|
|
|
/*
|
|
* An example from 3.6.x where all obufs got clobbered was when a
|
|
* monster used a bullwhip to disarm the hero of a two-handed weapon:
|
|
* "The ogre lord yanks Cleaver from your corpses!"
|
|
|
|
|
| hand = body_part(HAND);
|
|
| if (use_plural) // switches 'hand' from static buffer to an obuf
|
|
| hand = makeplural(hand);
|
|
...
|
|
| release_worn_item(); // triggers full inventory update for perm_invent
|
|
...
|
|
| pline(..., hand); // the obuf[] for "hands" was clobbered with the
|
|
| //+ partial formatting of an item from invent
|
|
*
|
|
* Another example was from writing a scroll without room in invent to
|
|
* hold it after being split from a stack of blank scrolls:
|
|
* "Oops! food rations out of your grasp!"
|
|
* hold_another_object() was passed 'the(aobjnam(newscroll, "slip"))'
|
|
* as an argument and that should have yielded
|
|
* "Oops! The scroll of <foo> slips out of your grasp!"
|
|
* but attempting to add the item to inventory triggered update for
|
|
* perm_invent and the result from 'the(...)' was clobbered by partial
|
|
* formatting of some inventory item. [It happened in a shop and the
|
|
* shk claimed ownership of the new scroll, but that wasn't relevant.]
|
|
* That got fixed earlier, by delaying update_inventory() during
|
|
* hold_another_object() rather than by avoiding using all the obufs.
|
|
*/
|
|
}
|
|
|
|
char *
|
|
obj_typename(int otyp)
|
|
{
|
|
char *buf = nextobuf();
|
|
struct objclass *ocl = &objects[otyp];
|
|
const char *actualn = OBJ_NAME(*ocl);
|
|
const char *dn = OBJ_DESCR(*ocl);
|
|
const char *un = ocl->oc_uname;
|
|
int nn = ocl->oc_name_known;
|
|
|
|
if (Role_if(PM_SAMURAI)) {
|
|
actualn = Japanese_item_name(otyp, actualn);
|
|
if (otyp == WOODEN_HARP || otyp == MAGIC_HARP)
|
|
dn = "koto";
|
|
}
|
|
/* generic items don't have an actual-name; we shouldn't ever be called
|
|
for those; pacify static analyzer without resorting to impossible() */
|
|
if (!actualn)
|
|
actualn = (otyp > 0 && otyp < MAXOCLASSES) ? "generic" : "object?";
|
|
|
|
buf[0] = '\0'; /* redundant */
|
|
switch (ocl->oc_class) {
|
|
case COIN_CLASS:
|
|
Strcpy(buf, "coin");
|
|
break;
|
|
case POTION_CLASS:
|
|
Strcpy(buf, "potion");
|
|
break;
|
|
case SCROLL_CLASS:
|
|
Strcpy(buf, "scroll");
|
|
break;
|
|
case WAND_CLASS:
|
|
Strcpy(buf, "wand");
|
|
break;
|
|
case SPBOOK_CLASS:
|
|
if (otyp != SPE_NOVEL) {
|
|
Strcpy(buf, "spellbook");
|
|
} else {
|
|
Strcpy(buf, !nn ? "book" : "novel");
|
|
nn = 0;
|
|
}
|
|
break;
|
|
case RING_CLASS:
|
|
Strcpy(buf, "ring");
|
|
break;
|
|
case AMULET_CLASS:
|
|
if (nn)
|
|
Strcpy(buf, actualn);
|
|
else
|
|
Strcpy(buf, "amulet");
|
|
if (un)
|
|
xcalled(buf, BUFSZ - (dn ? (int) strlen(dn) + 3 : 0), "", un);
|
|
if (dn)
|
|
Sprintf(eos(buf), " (%s)", dn);
|
|
return buf;
|
|
default:
|
|
if (nn) {
|
|
Strcpy(buf, actualn);
|
|
if (GemStone(otyp))
|
|
Strcat(buf, " stone");
|
|
if (un) /* 3: length of " (" + ")" which will enclose 'dn' */
|
|
xcalled(buf, BUFSZ - (dn ? (int) strlen(dn) + 3 : 0), "", 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)
|
|
xcalled(buf, BUFSZ, "", 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) /* 3: length of " (" + ")" which will enclose 'dn' */
|
|
xcalled(buf, BUFSZ - (dn ? (int) strlen(dn) + 3 : 0), "", 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(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;
|
|
}
|
|
|
|
/* typename for debugging feedback where data involved might be suspect */
|
|
char *
|
|
safe_typename(int otyp)
|
|
{
|
|
unsigned save_nameknown;
|
|
char *res = 0;
|
|
|
|
if (otyp < STRANGE_OBJECT || otyp >= NUM_OBJECTS
|
|
|| !OBJ_NAME(objects[otyp])) {
|
|
res = nextobuf();
|
|
Sprintf(res, "glorkum[%d]", otyp);
|
|
impossible("safe_typename: %s", res);
|
|
} else {
|
|
/* force it to be treated as fully discovered */
|
|
save_nameknown = objects[otyp].oc_name_known;
|
|
objects[otyp].oc_name_known = 1;
|
|
res = simple_typename(otyp);
|
|
objects[otyp].oc_name_known = save_nameknown;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
boolean
|
|
obj_is_pname(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 usually don't want to set dknown if it's not set already. */
|
|
char *
|
|
distant_name(
|
|
struct obj *obj, /* object to be formatted */
|
|
char *(*func)(OBJ_P)) /* formatting routine (usually xname or doname) */
|
|
{
|
|
char *str;
|
|
unsigned save_oid;
|
|
coordxy ox = 0, oy = 0;
|
|
/*
|
|
* (r * r): square of the x or y distance;
|
|
* (r * r) * 2: sum of squares of both x and y distances
|
|
* (r * r) * 2 - r: instead of a square extending from the hero,
|
|
* round the corners (so shorter distance imposed for diagonal).
|
|
*
|
|
* distu() matrix covering a range of 3+ for one quadrant:
|
|
* 16 17 - - -
|
|
* 9 10 13 18 -
|
|
* 4 5 8 13 -
|
|
* 1 2 5 10 17
|
|
* @ 1 4 9 16
|
|
* Theoretical r==1 would yield 1.
|
|
* r==2 yields 6, functionally equivalent to 5, a knight's jump,
|
|
* r==3, the xray range of the Eyes of the Overworld, yields 15.
|
|
*/
|
|
int r = (u.xray_range > 2) ? u.xray_range : 2,
|
|
neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */
|
|
|
|
/* setting o_id to 0 prevents xname() from adding T-shirt or apron
|
|
slogan, Hawaiian shirt motif, or candy wrapper label when called
|
|
with 'program_state.gameover' set; we want this suppression for
|
|
html-dump (not implemented in nethack) to prevent object-on-map
|
|
tooltips from including that extra text; also guards against a
|
|
potential change to minimal_xname() [indirectly used by attribute
|
|
disclosure] that propagates o_id rather than leave it 0, and
|
|
against a potential extra chance to browse the map with getpos()
|
|
during final disclosure (not currently implemented, nor planned) */
|
|
save_oid = obj->o_id;
|
|
if (program_state.gameover)
|
|
obj->o_id = 0;
|
|
|
|
/* this maybe-nearby part used to be replicated in multiple callers */
|
|
if (get_obj_location(obj, &ox, &oy, 0) && cansee(ox, oy)
|
|
&& (obj->oartifact || distu(ox, oy) <= neardist)) {
|
|
/* side-effects: treat as having been seen up close;
|
|
cansee() is True hence hero isn't Blind so if 'func' is
|
|
the usual doname or xname, obj->dknown will become set
|
|
and then for an artifact, find_artifact() will be called */
|
|
str = (*func)(obj);
|
|
} else {
|
|
/* prior to 3.6.1, this used to save current blindness state,
|
|
explicitly set state to hero-is-blind, make the call (which
|
|
won't set obj->dknown when blind), then restore the saved
|
|
value; but the Eyes of the Overworld override blindness and
|
|
would let characters wearing them get obj->dknown set for
|
|
distant items, so the external flag was added */
|
|
++gd.distantname;
|
|
str = (*func)(obj);
|
|
--gd.distantname;
|
|
}
|
|
|
|
obj->o_id = save_oid; /* reset to normal */
|
|
|
|
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(
|
|
boolean juice) /* whether or not to append " juice" to the name */
|
|
{
|
|
char *buf = nextobuf();
|
|
const char *fruit_nam = strstri(svp.pl_fruit, " of ");
|
|
|
|
if (fruit_nam)
|
|
fruit_nam += 4; /* skip past " of " */
|
|
else
|
|
fruit_nam = svp.pl_fruit; /* use it as is */
|
|
|
|
Sprintf(buf, "%s%s", makesingular(fruit_nam), juice ? " juice" : "");
|
|
return buf;
|
|
}
|
|
|
|
/* look up a named fruit by index (1..127) */
|
|
struct fruit *
|
|
fruit_from_indx(int indx)
|
|
{
|
|
struct fruit *f;
|
|
|
|
for (f = gf.ffruit; f; f = f->nextf)
|
|
if (f->fid == indx)
|
|
break;
|
|
return f;
|
|
}
|
|
|
|
/* look up a named fruit by name */
|
|
struct fruit *
|
|
fruit_from_name(
|
|
const char *fname,
|
|
boolean exact, /* False: prefix or exact match, True: exact match only */
|
|
int *highest_fid) /* optional output; only valid if 'fname' isn't found */
|
|
{
|
|
struct fruit *f, *tentativef;
|
|
char *altfname;
|
|
unsigned k;
|
|
/*
|
|
* note: named fruits are case-sensitive...
|
|
*/
|
|
|
|
if (highest_fid)
|
|
*highest_fid = 0;
|
|
/* first try for an exact match */
|
|
for (f = gf.ffruit; f; f = f->nextf)
|
|
if (!strcmp(f->fname, fname))
|
|
return f;
|
|
else if (highest_fid && f->fid > *highest_fid)
|
|
*highest_fid = f->fid;
|
|
|
|
/* didn't match as-is; if caller is willing to accept a prefix
|
|
match, try to find one; we want to find the longest prefix that
|
|
matches, not the first */
|
|
if (!exact) {
|
|
tentativef = 0;
|
|
for (f = gf.ffruit; f; f = f->nextf) {
|
|
k = Strlen(f->fname);
|
|
if (!strncmp(f->fname, fname, k)
|
|
&& (!fname[k] || fname[k] == ' ')
|
|
&& (!tentativef || k > strlen(tentativef->fname)))
|
|
tentativef = f;
|
|
}
|
|
f = tentativef;
|
|
}
|
|
/* if we still don't have a match, try singularizing the target;
|
|
for exact match, that's trivial, but for prefix, it's hard */
|
|
if (!f) {
|
|
altfname = makesingular(fname);
|
|
for (f = gf.ffruit; f; f = f->nextf) {
|
|
if (!strcmp(f->fname, altfname))
|
|
break;
|
|
}
|
|
releaseobuf(altfname);
|
|
}
|
|
if (!f && !exact) {
|
|
char fnamebuf[BUFSZ], *p;
|
|
unsigned fname_k = Strlen(fname); /* length of assumed plural fname */
|
|
|
|
tentativef = 0;
|
|
for (f = gf.ffruit; f; f = f->nextf) {
|
|
k = Strlen(f->fname);
|
|
/* reload fnamebuf[] each iteration in case it gets modified;
|
|
there's no need to recalculate fname_k */
|
|
Strcpy(fnamebuf, fname);
|
|
/* bug? if singular of fname is longer than plural,
|
|
failing the 'fname_k > k' test could skip a viable
|
|
candidate; unfortunately, we can't singularize until
|
|
after stripping off trailing stuff and we can't get
|
|
accurate fname_k until fname has been singularized;
|
|
compromise and use 'fname_k >= k' instead of '>',
|
|
accepting 1 char length discrepancy without risking
|
|
false match (I hope...) */
|
|
if (fname_k >= k && (p = strchr(&fnamebuf[k], ' ')) != 0) {
|
|
*p = '\0'; /* truncate at 1st space past length of f->fname */
|
|
altfname = makesingular(fnamebuf);
|
|
k = Strlen(altfname); /* actually revised 'fname_k' */
|
|
if (!strcmp(f->fname, altfname)
|
|
&& (!tentativef || k > strlen(tentativef->fname)))
|
|
tentativef = f;
|
|
releaseobuf(altfname); /* avoid churning through all obufs */
|
|
}
|
|
}
|
|
f = tentativef;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
/* sort the named-fruit linked list by fruit index number */
|
|
void
|
|
reorder_fruit(boolean forward)
|
|
{
|
|
struct fruit *f, *allfr[1 + 127];
|
|
int i, j, k = SIZE(allfr);
|
|
|
|
for (i = 0; i < k; ++i)
|
|
allfr[i] = (struct fruit *) 0;
|
|
for (f = gf.ffruit; f; f = f->nextf) {
|
|
/* without sanity checking, this would reduce to 'allfr[f->fid]=f' */
|
|
j = f->fid;
|
|
if (j < 1 || j >= k) {
|
|
impossible("reorder_fruit: fruit index (%d) out of range", j);
|
|
return; /* don't sort after all; should never happen... */
|
|
} else if (allfr[j]) {
|
|
impossible("reorder_fruit: duplicate fruit index (%d)", j);
|
|
return;
|
|
}
|
|
allfr[j] = f;
|
|
}
|
|
gf.ffruit = 0; /* reset linked list; we're rebuilding it from scratch */
|
|
/* slot [0] will always be empty; must start 'i' at 1 to avoid
|
|
[k - i] being out of bounds during first iteration */
|
|
for (i = 1; i < k; ++i) {
|
|
/* for forward ordering, go through indices from high to low;
|
|
for backward ordering, go from low to high */
|
|
j = forward ? (k - i) : i;
|
|
if (allfr[j]) {
|
|
allfr[j]->nextf = gf.ffruit;
|
|
gf.ffruit = allfr[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add "<pfx> called <sfx>" to end of buf, truncating if necessary */
|
|
staticfn void
|
|
xcalled(
|
|
char *buf, /* eos(obuf) or eos(&obuf[PREFIX]) */
|
|
int siz, /* BUFSZ or BUFSZ-PREFIX */
|
|
const char *pfx, /* usually class string, sometimes more specific */
|
|
const char *sfx) /* user assigned type name */
|
|
{
|
|
int bufsiz = siz - 1 - (int) strlen(buf),
|
|
pfxlen = (int) (strlen(pfx) + sizeof " called " - sizeof "");
|
|
|
|
if (pfxlen > bufsiz)
|
|
panic("xcalled: not enough room for prefix (%d > %d)",
|
|
pfxlen, bufsiz);
|
|
|
|
Sprintf(eos(buf), "%s called %.*s", pfx, bufsiz - pfxlen, sfx);
|
|
}
|
|
|
|
char *
|
|
xname(struct obj *obj)
|
|
{
|
|
return xname_flags(obj, CXN_NORMAL);
|
|
}
|
|
|
|
staticfn char *
|
|
xname_flags(
|
|
struct obj *obj,
|
|
unsigned cxn_flags) /* bitmask of CXN_xxx values */
|
|
{
|
|
char *buf;
|
|
char *obufp, *buf_end, *buf_eos;
|
|
size_t bufspaceleft;
|
|
int typ = obj->otyp;
|
|
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) && !(cxn_flags & CXN_SINGULAR);
|
|
boolean known, dknown, bknown;
|
|
|
|
gx.xnamep = nextobuf();
|
|
/* set up primary work buffer; the first 'PREFIX' bytes are set
|
|
aside for use by doname() */
|
|
buf = gx.xnamep + PREFIX; /* leave room for "17 -3 " */
|
|
buf_end = gx.xnamep + BUFSZ - 1; /* last byte within the obuf[] */
|
|
buf[0] = '\0';
|
|
ConcUpdate(buf); /* set buf_eos and bufspaceleft */
|
|
|
|
if (Role_if(PM_SAMURAI)) {
|
|
actualn = Japanese_item_name(typ, actualn);
|
|
if (typ == WOODEN_HARP || typ == MAGIC_HARP)
|
|
dn = "koto";
|
|
}
|
|
/* generic items don't have an actual-name; we shouldn't ever be called
|
|
for those; pacify static analyzer without resorting to impossible() */
|
|
if (!actualn)
|
|
actualn = (typ > 0 && typ < MAXOCLASSES) ? "generic" : "object?";
|
|
/* 3.6.2: this used to be part of 'dn's initialization, but it
|
|
needs to come after possibly overriding 'actualn' */
|
|
if (!dn)
|
|
dn = actualn;
|
|
|
|
/*
|
|
* 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 && !gd.distantname)
|
|
obj->dknown = 1;
|
|
if (Role_if(PM_CLERIC))
|
|
obj->bknown = 1; /* avoid set_bknown() to bypass update_inventory() */
|
|
|
|
if (iflags.override_ID) {
|
|
known = dknown = bknown = TRUE;
|
|
nn = 1;
|
|
} else {
|
|
known = obj->known;
|
|
dknown = obj->dknown;
|
|
bknown = obj->bknown;
|
|
}
|
|
|
|
/*
|
|
* Maybe find a previously unseen artifact.
|
|
*
|
|
* Assumption 1: if an artifact object is being formatted, it is
|
|
* being shown to the hero (on floor, or looking into container,
|
|
* or probing a monster, or seeing a monster wield it).
|
|
* Assumption 2: if in a pile that has been stepped on, the
|
|
* artifact won't be noticed for cases where the pile to too deep
|
|
* to be auto-shown, unless the player explicitly looks at that
|
|
* spot (via ':'). Might need to make an exception somehow (at
|
|
* the point where the decision whether to auto-show gets made?)
|
|
* when an artifact is on the top of the pile.
|
|
* Assumption 3: since this is used for livelog events, not being
|
|
* 100% correct won't negatively affect the player's current game.
|
|
*
|
|
* We use the real obj->dknown rather than the override_ID variant
|
|
* so that wizard-mode ^I doesn't cause a not-yet-seen artifact in
|
|
* inventory (picked up while blind, still blind) to become found.
|
|
*/
|
|
if (obj->oartifact && obj->dknown)
|
|
find_artifact(obj);
|
|
|
|
if (obj_is_pname(obj))
|
|
goto nameit;
|
|
|
|
/* Some classes use strcpy(buf, something)+strcat(buf, otherthing).
|
|
In those cases, ConcUpdate() is needed in between if Concat()
|
|
will be used for the strcat() part. Other classes just use
|
|
strcpy(buf, something) and the ConcUpdate() can be deferred
|
|
until after the switch. */
|
|
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)
|
|
xcalled(buf, BUFSZ - PREFIX, "amulet", un);
|
|
else
|
|
Sprintf(buf, "%s amulet", dn);
|
|
break;
|
|
case WEAPON_CLASS:
|
|
if (is_poisonable(obj) && obj->opoisoned)
|
|
Strcpy(buf, "poisoned ");
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case VENOM_CLASS:
|
|
case TOOL_CLASS:
|
|
/* note: lenses or towel prefix would overwrite poisoned weapon
|
|
prefix if both were simultaneously possible, but they aren't */
|
|
if (typ == LENSES)
|
|
Strcpy(buf, "pair of ");
|
|
else if (is_wet_towel(obj))
|
|
Strcpy(buf, (obj->spe < 3) ? "moist " : "wet ");
|
|
|
|
if (!dknown)
|
|
Strcat(buf, dn);
|
|
else if (nn)
|
|
Strcat(buf, actualn);
|
|
else if (un)
|
|
xcalled(buf, BUFSZ - PREFIX, dn, un);
|
|
else
|
|
Strcat(buf, dn);
|
|
ConcUpdate(buf);
|
|
|
|
if (typ == FIGURINE && omndx != NON_PM) {
|
|
char anbuf[10]; /* [4] would be enough: 'a','n',' ','\0' */
|
|
const char *pm_name = obj_pmname(obj);
|
|
|
|
ConcatF2(buf, 0, " of %s%s", just_an(anbuf, pm_name), pm_name);
|
|
} else if (is_wet_towel(obj)) {
|
|
if (wizard)
|
|
ConcatF1(buf, 0, " (%d)", obj->spe);
|
|
}
|
|
break;
|
|
case ARMOR_CLASS:
|
|
/* depends on order of the dragon scales objects */
|
|
if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) {
|
|
Sprintf(buf, "set of %s", actualn);
|
|
break;
|
|
} else if (is_boots(obj) || is_gloves(obj)) {
|
|
Strcpy(buf, "pair of ");
|
|
/*FALLTHRU*/
|
|
} else if (is_shield(obj) && !dknown) {
|
|
if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) {
|
|
Strcpy(buf, "shield");
|
|
break;
|
|
} else if (obj->otyp == SHIELD_OF_REFLECTION) {
|
|
Strcpy(buf, "smooth shield");
|
|
break;
|
|
}
|
|
}
|
|
ConcUpdate(buf);
|
|
|
|
if (nn)
|
|
Concat(buf, 0, actualn);
|
|
else if (un)
|
|
xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un);
|
|
else
|
|
Concat(buf, 0, dn);
|
|
break;
|
|
case FOOD_CLASS:
|
|
/* we could include partly-eaten-hack on fruit but don't need to */
|
|
if (typ == SLIME_MOLD) {
|
|
struct fruit *f = fruit_from_indx(obj->spe);
|
|
|
|
if (!f) {
|
|
impossible("Bad fruit #%d?", obj->spe);
|
|
Strcpy(buf, "fruit");
|
|
} else {
|
|
/* fruit name is limited in length to PL_FSIZ; converting
|
|
to/from singular/plural might increase the length a
|
|
little but not enough to pose a risk of overflowing buf */
|
|
Strcpy(buf, f->fname);
|
|
if (pluralize) {
|
|
/* ick: already pluralized fruit names are allowed--we
|
|
want to try to avoid adding a redundant plural suffix;
|
|
double ick: makesingular() and makeplural() each use
|
|
and return an obuf but we don't want any particular
|
|
xname() call to consume more than one of those
|
|
[note: makeXXX() will be fully evaluated and done with
|
|
'buf' before strcpy() touches its output buffer] */
|
|
Strcpy(buf, obufp = makesingular(buf));
|
|
releaseobuf(obufp);
|
|
Strcpy(buf, obufp = makeplural(buf));
|
|
releaseobuf(obufp);
|
|
|
|
pluralize = FALSE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (iflags.partly_eaten_hack && obj->oeaten) {
|
|
/* normally "partly eaten" is supplied by doname() when
|
|
appropriate and omitted by xname(); shrink_glob() wants
|
|
it but uses Yname2() -> yname() -> xname() rather than
|
|
doname() so we've added an external flag to request it */
|
|
Concat(buf, 0, "partly eaten ");
|
|
}
|
|
if (obj->globby) { /* 3.7 added "medium" to replace no-prefix */
|
|
ConcatF2(buf, 0, "%s %s", (obj->owt <= 100) ? "small"
|
|
: (obj->owt <= 300) ? "medium"
|
|
: (obj->owt <= 500) ? "large"
|
|
: "very large",
|
|
actualn);
|
|
break;
|
|
}
|
|
|
|
Concat(buf, 0, 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) {
|
|
char anbuf[10];
|
|
const char *statue_pmname = obj_pmname(obj);
|
|
|
|
Snprintf(buf, bufspaceleft, "%s%s of %s%s",
|
|
(Role_if(PM_ARCHEOLOGIST)
|
|
&& (obj->spe & CORPSTAT_HISTORIC) != 0) ? "historic "
|
|
: "",
|
|
actualn,
|
|
type_is_pname(&mons[omndx]) ? ""
|
|
: the_unique_pm(&mons[omndx]) ? "the "
|
|
: just_an(anbuf, statue_pmname),
|
|
statue_pmname);
|
|
} else if (typ == BOULDER && obj->next_boulder == 1) {
|
|
/* sometimes caller wants "next boulder" rather than just
|
|
"boulder" (when pushing against a pile of more than one);
|
|
originally we just tested for non-0 but checking for 1 is
|
|
more robust because the default value for that overloaded
|
|
field (obj->corpsenm) is NON_PM (-1) rather than 0 */
|
|
Strcat(strcpy(buf, "next "), actualn); /* "next boulder" */
|
|
/* once "next boulder" occurs, subsequent messages should just
|
|
use ordinary "boulder" */
|
|
obj->next_boulder = 0;
|
|
} else {
|
|
Strcpy(buf, actualn); /* "boulder" or "statue" */
|
|
}
|
|
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 {
|
|
xcalled(buf, BUFSZ - PREFIX, "", 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) {
|
|
xcalled(buf, BUFSZ - PREFIX, "", 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)
|
|
xcalled(buf, BUFSZ - PREFIX, "wand", un);
|
|
else
|
|
Sprintf(buf, "%s wand", dn);
|
|
break;
|
|
case SPBOOK_CLASS:
|
|
if (typ == SPE_NOVEL) { /* 3.6 tribute */
|
|
if (!dknown)
|
|
Strcpy(buf, "book");
|
|
else if (nn)
|
|
Strcpy(buf, actualn);
|
|
else if (un)
|
|
xcalled(buf, BUFSZ - PREFIX, "novel", un);
|
|
else
|
|
Sprintf(buf, "%s book", dn);
|
|
break;
|
|
/* end of tribute */
|
|
} else if (!dknown) {
|
|
Strcpy(buf, "spellbook");
|
|
} else if (nn) {
|
|
if (typ != SPE_BOOK_OF_THE_DEAD)
|
|
Strcpy(buf, "spellbook of ");
|
|
Strcat(buf, actualn);
|
|
} else if (un) {
|
|
xcalled(buf, BUFSZ - PREFIX, "spellbook", 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)
|
|
xcalled(buf, BUFSZ - PREFIX, "ring", 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)
|
|
xcalled(buf, BUFSZ - PREFIX, rock, un);
|
|
else
|
|
Sprintf(buf, "%s %s", dn, rock);
|
|
} else {
|
|
Strcpy(buf, actualn);
|
|
if (GemStone(typ))
|
|
Strcat(buf, " stone");
|
|
}
|
|
break;
|
|
} /* gem */
|
|
default:
|
|
Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe);
|
|
impossible("xname_flags: %s", buf);
|
|
break;
|
|
} /* switch */
|
|
|
|
/* check whether we've already gone out of bounds of the obuf[], prior
|
|
to pluralization and end-of-game shirt and apron text */
|
|
buf_eos = eos(buf);
|
|
if (buf_eos > buf_end) {
|
|
/* PREFIX is bigger than 6 so there will always be room within the
|
|
obuf[] in front of buf to insert "buf[]="; strncpy(,,N) doesn't
|
|
add '\0' terminator unless fewer than N chars are copied, which
|
|
is what we want, but gcc complains about that so use memcpy() */
|
|
paniclog("xname", (char *) memcpy(buf - 6, "buf[]=", 6));
|
|
panic("xname: buffer overflow before appending name.");
|
|
/*NOTREACHED*/
|
|
}
|
|
bufspaceleft = (size_t) (buf_end - buf_eos);
|
|
|
|
/* if the name should be plural, do that now, after overflow check;
|
|
it could make buf[] become shorter */
|
|
if (pluralize) {
|
|
obufp = makeplural(buf);
|
|
buf[0] = '\0'; /* replace the whole string */
|
|
ConcUpdate(buf); /* reset buf_eos and bufspaceleft */
|
|
Concat(buf, 0, obufp);
|
|
releaseobuf(obufp);
|
|
}
|
|
|
|
/* give some extra information when game is over; for end-of-game
|
|
attribute disclosure in wizard mode, ysimple_name() calls
|
|
minimal_xname() which passes us a dummy object with o_id==0;
|
|
tshirt_text(), apron_text(), and so forth base their result on
|
|
o_id and would give inconsistent information compared to what
|
|
just got shown for inventory disclosure; fortunately, we want to
|
|
avoid the 'with text' part of
|
|
"You were acid resistant because of your alchemy smock \
|
|
with text \"Kiss the cook\"."
|
|
when disclosing attributes anyway */
|
|
if (program_state.gameover && obj->o_id && bufspaceleft > 0) {
|
|
const char *lbl;
|
|
char tmpbuf[BUFSZ];
|
|
|
|
/* disclose without breaking illiterate conduct, but mainly tip off
|
|
players who aren't aware that something readable is present */
|
|
switch (obj->otyp) {
|
|
case T_SHIRT:
|
|
case ALCHEMY_SMOCK:
|
|
ConcatF1(buf, 0, " with text \"%s\"",
|
|
(obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf)
|
|
: apron_text(obj, tmpbuf));
|
|
break;
|
|
case CANDY_BAR:
|
|
lbl = candy_wrapper_text(obj);
|
|
if (*lbl)
|
|
ConcatF1(buf, 0, " labeled \"%s\"", lbl);
|
|
break;
|
|
case HAWAIIAN_SHIRT:
|
|
ConcatF1(buf, 0, " with %s motif",
|
|
an(hawaiian_motif(obj, tmpbuf)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (has_oname(obj) && dknown) {
|
|
Concat(buf, 0, " named ");
|
|
|
|
/* jump directly here if obj passes the has-personal-name test */
|
|
nameit:
|
|
/*assert(has_oname(obj));*/
|
|
obufp = eos(buf); /* remember where the name will start */
|
|
Concat(buf, 0, ONAME(obj));
|
|
/* downcase "The" in "<quest-artifact-item> named The ..." */
|
|
if (obj->oartifact && !strncmp(obufp, "The ", 4))
|
|
*obufp = lowc(*obufp); /* change 'T' in "The " to 't' */
|
|
}
|
|
|
|
if (!strncmpi(buf, "the ", 4))
|
|
buf += 4;
|
|
|
|
buf_eos = eos(buf); /* pointer to '\0' terminator somewhere in obuf[] */
|
|
if (buf_eos >= buf_end) { /* ('>' shouldn't be possible) */
|
|
static int xname_full = 0;
|
|
|
|
/* we want a record of something needing more buffer space than
|
|
anticipated; since we aren't panicking here, this could happen
|
|
repeatedly and we don't want to spam the paniclog file */
|
|
if (!xname_full++) {
|
|
paniclog("xname", (char *) memcpy(buf - 6, "buf[]=", 6));
|
|
/* 'PREFIX' ought to be 'PREFIX+4' if we stripped leading "the" */
|
|
paniclog("xname", "used up entire obuf[PREFIX..BUFSX-1]");
|
|
}
|
|
}
|
|
|
|
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
|
|
*/
|
|
staticfn char *
|
|
minimal_xname(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 (iflags.override_ID)
|
|
objects[otyp].oc_name_known = 1;
|
|
else 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 = cg.zeroobj;
|
|
bareobj.otyp = otyp;
|
|
bareobj.oclass = obj->oclass;
|
|
bareobj.dknown = (obj->dknown || iflags.override_ID) ? 1 : 0;
|
|
/* 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 */
|
|
/* for a boulder, leave corpsenm as 0; non-zero produces "next boulder" */
|
|
if (otyp != BOULDER)
|
|
bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */
|
|
/* but suppressing fruit details leads to "bad fruit #0"
|
|
[perhaps we should force "slime mold" rather than use xname?] */
|
|
if (obj->otyp == SLIME_MOLD)
|
|
bareobj.spe = obj->spe;
|
|
|
|
bufp = distant_name(&bareobj, xname);
|
|
/* undo forced setting of bareobj.blessed for cleric (priest[ess]);
|
|
bufp is an obuf[] so a pointer into the middle of that is viable */
|
|
if (!strncmp(bufp, "uncursed ", 9))
|
|
bufp += 9;
|
|
|
|
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(struct obj *obj)
|
|
{
|
|
char tmpbuf[BUFSZ];
|
|
char *onm = xname(obj);
|
|
|
|
if (gm.m_shot.n > 1 && gm.m_shot.o == obj->otyp) {
|
|
/* "the Nth arrow"; value will eventually be passed to an() or
|
|
The(), both of which correctly handle this "the " prefix */
|
|
Sprintf(tmpbuf, "the %d%s ", gm.m_shot.i, ordin(gm.m_shot.i));
|
|
onm = strprepend(onm, tmpbuf);
|
|
}
|
|
return onm;
|
|
}
|
|
|
|
/* used for naming "the unique_item" instead of "a unique_item" */
|
|
boolean
|
|
the_unique_obj(struct obj *obj)
|
|
{
|
|
boolean known = (obj->known || iflags.override_ID);
|
|
|
|
if (!obj->dknown && !iflags.override_ID)
|
|
return FALSE;
|
|
else if (obj->otyp == FAKE_AMULET_OF_YENDOR && !known)
|
|
return TRUE; /* lie */
|
|
else
|
|
return (boolean) (objects[obj->otyp].oc_unique
|
|
&& (known || obj->otyp == AMULET_OF_YENDOR));
|
|
}
|
|
|
|
/* should monster type be prefixed with "the"? (mostly used for corpses) */
|
|
boolean
|
|
the_unique_pm(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_CLERIC] || 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;
|
|
}
|
|
|
|
staticfn void
|
|
add_erosion_words(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 "
|
|
: is_crackable(obj) ? "cracked "
|
|
: "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 ");
|
|
}
|
|
/* note: it is possible for an item to be both eroded and erodeproof
|
|
(cursed scroll of destroy armor read while confused erodeproofs an
|
|
item of armor without repairing existing erosion) */
|
|
if (rknown && obj->oerodeproof)
|
|
Strcat(prefix, iscrys ? "fixed "
|
|
: is_rustprone(obj) ? "rustproof "
|
|
: is_corrodeable(obj) ? "corrodeproof "
|
|
: is_flammable(obj) ? "fireproof "
|
|
: is_crackable(obj) ? "tempered " /* hardened */
|
|
: "");
|
|
}
|
|
|
|
/* used to prevent rust on items where rust makes no difference */
|
|
boolean
|
|
erosion_matters(struct obj *obj)
|
|
{
|
|
switch (obj->oclass) {
|
|
case TOOL_CLASS:
|
|
/* it's possible for a rusty weptool to be polymorphed into some
|
|
non-weptool iron tool, in which case the rust implicitly goes
|
|
away, but it's also possible for it to be polymorphed into a
|
|
non-iron tool, in which case rust also implicitly goes away,
|
|
so there's no particular reason to try to handle the first
|
|
instance differently [this comment belongs in poly_obj()...] */
|
|
return is_weptool(obj) ? TRUE : FALSE;
|
|
case WEAPON_CLASS:
|
|
case ARMOR_CLASS:
|
|
case BALL_CLASS:
|
|
case CHAIN_CLASS:
|
|
return TRUE;
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#define DONAME_WITH_PRICE 1
|
|
#define DONAME_VAGUE_QUAN 2
|
|
#define DONAME_FOR_MENU 4 /* [not used anywhere yet] */
|
|
|
|
/* core of doname() */
|
|
staticfn char *
|
|
doname_base(
|
|
struct obj *obj, /* object to format */
|
|
unsigned doname_flags) /* special case requests */
|
|
{
|
|
boolean ispoisoned = FALSE,
|
|
with_price = (doname_flags & DONAME_WITH_PRICE) != 0,
|
|
vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0,
|
|
for_menu = (doname_flags & DONAME_FOR_MENU) != 0;
|
|
boolean known, dknown, cknown, bknown, lknown,
|
|
fake_arti, force_the;
|
|
char prefix[PREFIX];
|
|
char tmpbuf[PREFIX + 1]; /* for when we have to add something at
|
|
* the start of prefix instead of the
|
|
* end (Strcat is used on the end) */
|
|
const char *aname = 0;
|
|
int omndx = obj->corpsenm;
|
|
char *bp;
|
|
char *bp_eos, *bp_end;
|
|
size_t bpspaceleft;
|
|
|
|
/* 'bp' will be within an obuf[] rather than at the start of one,
|
|
usually (but not always) pointing at &obuf[PREFIX];
|
|
gx.xnamep always points to the start of that buffer;
|
|
'bp_eos' and 'bpspaceleft' are used and updated by Concat*() macros */
|
|
bp = xname(obj);
|
|
bp_end = gx.xnamep + BUFSZ - 1;
|
|
bp_eos = eos(bp);
|
|
assert(bp_end >= bp_eos); /* ok provided xname() bounds checking works */
|
|
/* size_t cast: convert signed ptrdiff_t to unsigned size_t */
|
|
bpspaceleft = (size_t) (bp_end - bp_eos);
|
|
|
|
if (iflags.override_ID) {
|
|
known = dknown = cknown = bknown = lknown = TRUE;
|
|
} else {
|
|
known = obj->known;
|
|
dknown = obj->dknown;
|
|
cknown = obj->cknown;
|
|
bknown = obj->bknown;
|
|
lknown = obj->lknown;
|
|
}
|
|
|
|
/* When using xname, we want "poisoned arrow", and when using
|
|
* doname, we want "poisoned +0 arrow". This kludge is about the only
|
|
* way to do it, at least until someone overhauls xname() and doname(),
|
|
* combining both into one function taking a parameter.
|
|
*/
|
|
/* must check opoisoned--someone can have a weirdly-named fruit */
|
|
if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) {
|
|
bp += 9; /* doesn't affect bp_eos or bpspaceleft */
|
|
ispoisoned = TRUE;
|
|
}
|
|
|
|
/* fruits are allowed to be given artifact names; when that happens,
|
|
format the name like the corresponding artifact, which may or may not
|
|
want "the" prefix and when it doesn't, avoid "a"/"an" prefix too */
|
|
fake_arti = (obj->otyp == SLIME_MOLD
|
|
&& (aname = artifact_name(bp, (short *) 0, FALSE)) != 0);
|
|
force_the = (fake_arti && !strncmpi(aname, "the ", 4));
|
|
|
|
prefix[0] = '\0';
|
|
if (obj->quan != 1L) {
|
|
if (dknown || !vague_quan)
|
|
Sprintf(prefix, "%ld ", obj->quan);
|
|
else
|
|
Strcpy(prefix, "some ");
|
|
} else if (obj->otyp == CORPSE) {
|
|
/* skip article prefix for corpses [else corpse_xname()
|
|
would have to be taught how to strip it off again] */
|
|
;
|
|
} else if (force_the || obj_is_pname(obj) || the_unique_obj(obj)) {
|
|
if (!strncmpi(bp, "the ", 4))
|
|
bp += 4; /* doesn't affect bp_eos or bpspaceleft */
|
|
Strcpy(prefix, "the ");
|
|
} else if (!fake_arti) {
|
|
/* default prefix */
|
|
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);
|
|
horn of plenty isn't a container but is close enough */
|
|
&& ((obj->otyp == BAG_OF_TRICKS || obj->otyp == HORN_OF_PLENTY)
|
|
? (obj->spe == 0 && !known)
|
|
/* not a bag of tricks or horn of plenty: it's empty if
|
|
it is a container that 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 (!flags.implicit_uncursed
|
|
/* For most items with charges or +/-, if you know how many
|
|
* charges are left or what the +/- is, then you must have
|
|
* totally identified the item, so "uncursed" is unnecessary,
|
|
* because an identified object not described as "blessed" or
|
|
* "cursed" must be uncursed.
|
|
*
|
|
* If the charges or +/- is not known, "uncursed" must be
|
|
* printed to avoid ambiguity between an item whose curse
|
|
* status is unknown, and an item known to be uncursed.
|
|
*/
|
|
|| ((!known || !objects[obj->otyp].oc_charged
|
|
|| obj->oclass == ARMOR_CLASS
|
|
|| obj->oclass == RING_CLASS)
|
|
#ifdef MAIL_STRUCTURES
|
|
&& obj->otyp != SCR_MAIL
|
|
#endif
|
|
&& obj->otyp != FAKE_AMULET_OF_YENDOR
|
|
&& obj->otyp != AMULET_OF_YENDOR
|
|
&& !Role_if(PM_CLERIC)))
|
|
Strcat(prefix, "uncursed ");
|
|
}
|
|
|
|
/* "a large trapped box" would perhaps be more correct; [no!]
|
|
what about ``(obj->tknown && !obj->otrapped)''? shouldn't that
|
|
yield "a non-trapped large box"? (not "an untrapped large box");
|
|
TODO: this should be ``(Is_box(obj) || obj->otyp == TIN) && ...''
|
|
but at present there's no way to set obj->tknown for tins */
|
|
if (Is_box(obj) && obj->otrapped && obj->tknown && obj->dknown)
|
|
Strcat(prefix,"trapped ");
|
|
if (lknown && Is_box(obj)) {
|
|
if (obj->obroken)
|
|
/* 3.6.0 used "unlockable" here but that could be misunderstood
|
|
to mean "capable of being unlocked" rather than the intended
|
|
"not capable of being locked" */
|
|
Strcat(prefix, "broken ");
|
|
else if (obj->olocked)
|
|
Strcat(prefix, "locked ");
|
|
else
|
|
Strcat(prefix, "unlocked ");
|
|
}
|
|
|
|
if (obj->greased)
|
|
Strcat(prefix, "greased ");
|
|
|
|
if (cknown && Has_contents(obj) && bpspaceleft > 0) {
|
|
/* we count the number of separate stacks, which corresponds
|
|
to the number of inventory slots needed to be able to take
|
|
everything out if no merges occur */
|
|
long itemcount = count_contents(obj, FALSE, FALSE, TRUE, FALSE);
|
|
|
|
ConcatF2(bp, 0, " containing %ld item%s", itemcount, plur(itemcount));
|
|
}
|
|
|
|
switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) {
|
|
case AMULET_CLASS:
|
|
if (obj->owornmask & W_AMUL)
|
|
Concat(bp, 0, " (being worn)");
|
|
break;
|
|
case ARMOR_CLASS:
|
|
if (obj->owornmask & W_ARMOR) {
|
|
Concat(bp, 0,
|
|
(obj == uskin) ? " (embedded in your skin)"
|
|
/* in case of perm_invent update while Wear/Takeoff
|
|
is in progress; check doffing() before donning()
|
|
because donning() returns True for both cases */
|
|
: doffing(obj) ? " (being doffed)"
|
|
: donning(obj) ? " (being donned)"
|
|
: " (being worn)");
|
|
/* we just added a parenthesized phrase, but the right paren
|
|
might be absent if the appended string got truncated */
|
|
if (bp_eos[-1] == ')') {
|
|
/* slippery fingers is an intrinsic condition of the hero
|
|
rather than extrinsic condition of objects, but gloves
|
|
are described as slippery when hero has slippery fingers */
|
|
if (obj == uarmg && Glib) /* just appended "(something)",
|
|
* replace paren, changing that
|
|
* to be "(something; slippery)" */
|
|
Concat(bp, 1, "; slippery)");
|
|
}
|
|
if (bp_eos[-1] == ')') {
|
|
/* there could be light-emitting artifact gloves someday,
|
|
so add 'lit' separately from 'slippery' rather than via
|
|
'else if' after uarmg+Glib */
|
|
if (!Blind && obj->lamplit && artifact_light(obj))
|
|
ConcatF1(bp, 1, ", %s lit)", arti_light_description(obj));
|
|
}
|
|
}
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
case WEAPON_CLASS:
|
|
if (ispoisoned)
|
|
Strcat(prefix, "poisoned ");
|
|
add_erosion_words(obj, prefix);
|
|
if (known) {
|
|
Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */
|
|
}
|
|
break;
|
|
case TOOL_CLASS:
|
|
if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */
|
|
Concat(bp, 0, " (being worn)");
|
|
break;
|
|
}
|
|
if (obj->otyp == LEASH && obj->leashmon != 0) {
|
|
struct monst *mlsh = find_mid(obj->leashmon, FM_FMON);
|
|
|
|
if (mlsh && !DEADMONSTER(mlsh)) {
|
|
ConcatF1(bp, 0, " (attached to %s)", noit_mon_nam(mlsh));
|
|
} else {
|
|
if (mlsh) /*&& DEADMONSTER(mlsh)*/
|
|
impossible("leashed %s #%u is dead",
|
|
mon_pmname(mlsh), (unsigned) obj->leashmon);
|
|
else
|
|
impossible("leashed monster #%u not found",
|
|
(unsigned) obj->leashmon);
|
|
obj->leashmon = 0;
|
|
}
|
|
break;
|
|
}
|
|
if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
|
|
char suffix[20]; /* longest value is "s attached" */
|
|
|
|
/* separately formatted suffix avoids need for ConcatF3() */
|
|
Sprintf(suffix, "%s%s", plur(obj->spe),
|
|
!obj->lamplit ? " attached" : ", lit");
|
|
ConcatF2(bp, 0, " (%d of 7 candle%s)", obj->spe, suffix);
|
|
break;
|
|
} else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP
|
|
|| obj->otyp == BRASS_LANTERN || Is_candle(obj)) {
|
|
if (Is_candle(obj)) {
|
|
anything timer;
|
|
long full_burn_time = 20L * (long) objects[obj->otyp].oc_cost,
|
|
turns_left = obj->age;
|
|
|
|
if (obj->lamplit) {
|
|
timer = cg.zeroany;
|
|
timer.a_obj = obj;
|
|
/* without this, wishing for "lit candle" yields
|
|
"partly used candle (lit)" because the time it can
|
|
burn gets adjusted when it becomes lit; matters for
|
|
the message as it gets added to invent and also if it
|
|
gets snuffed out immediately (where it will end up as
|
|
not partly used after all) */
|
|
turns_left += peek_timer(BURN_OBJECT, &timer) - svm.moves;
|
|
}
|
|
if (turns_left < full_burn_time)
|
|
Strcat(prefix, "partly used ");
|
|
}
|
|
if (obj->lamplit)
|
|
Concat(bp, 0, " (lit)");
|
|
break;
|
|
}
|
|
if (objects[obj->otyp].oc_charged)
|
|
goto charges;
|
|
break;
|
|
case WAND_CLASS:
|
|
charges:
|
|
if (known)
|
|
ConcatF2(bp, 0, " (%d:%d)", (int) obj->recharged, obj->spe);
|
|
break;
|
|
case POTION_CLASS:
|
|
if (obj->otyp == POT_OIL && obj->lamplit)
|
|
Concat(bp, 0, " (lit)");
|
|
break;
|
|
case RING_CLASS:
|
|
ring: /* normal rings reach here 'naturally'; meat ring jumps here */
|
|
if (obj->owornmask & W_RINGR)
|
|
Concat(bp, 0, " (on right ");
|
|
if (obj->owornmask & W_RINGL)
|
|
Concat(bp, 0, " (on left ");
|
|
if (obj->owornmask & W_RING) /* either left or right */
|
|
ConcatF1(bp, 0,"%s)", body_part(HAND));
|
|
if (known && objects[obj->otyp].oc_charged) {
|
|
Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */
|
|
}
|
|
break;
|
|
case FOOD_CLASS:
|
|
if (obj->oeaten)
|
|
Strcat(prefix, "partly eaten ");
|
|
if (obj->otyp == CORPSE) {
|
|
/* (quan == 1) => want corpse_xname() to supply article,
|
|
(quan != 1) => already have count or "some" as prefix;
|
|
"corpse" is already in the buffer returned by xname() */
|
|
unsigned cxarg = (((obj->quan != 1L) ? 0 : CXN_ARTICLE)
|
|
| CXN_NOCORPSE);
|
|
char *cxstr, *save_xnamep;
|
|
|
|
/* corpse_xname() sets xnamep; callers other than doname_base()
|
|
itself shouldn't care about xnamep (pointer to start of
|
|
current obuf[]) but keep it accurate anyway */
|
|
save_xnamep = gx.xnamep;
|
|
cxstr = corpse_xname(obj, prefix, cxarg);
|
|
Sprintf(prefix, "%s ", cxstr);
|
|
/* avoid having doname(corpse) consume an extra obuf */
|
|
releaseobuf(cxstr);
|
|
gx.xnamep = save_xnamep;
|
|
} 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 (ismnum(omndx)
|
|
&& (known || (svm.mvitals[omndx].mvflags & MV_KNOWS_EGG))) {
|
|
Strcat(prefix, mons[omndx].pmnames[NEUTRAL]);
|
|
Strcat(prefix, " ");
|
|
if (obj->spe == 1)
|
|
Concat(bp, 0, " (laid by you)");
|
|
}
|
|
} else if (obj->otyp == MEAT_RING) {
|
|
goto ring;
|
|
}
|
|
break;
|
|
case BALL_CLASS:
|
|
case CHAIN_CLASS:
|
|
add_erosion_words(obj, prefix);
|
|
if (obj->owornmask & (W_BALL | W_CHAIN))
|
|
ConcatF1(bp, 0, " (%s to you)",
|
|
(obj->owornmask & W_BALL) ? "chained" : "attached");
|
|
break;
|
|
}
|
|
|
|
if ((obj->otyp == STATUE || obj->otyp == CORPSE || obj->otyp == FIGURINE)
|
|
&& wizard && iflags.wizmgender) {
|
|
int cgend = (obj->spe & CORPSTAT_GENDER),
|
|
mgend = ((cgend == CORPSTAT_MALE) ? MALE
|
|
: (cgend == CORPSTAT_FEMALE) ? FEMALE
|
|
: NEUTRAL);
|
|
|
|
ConcatF1(bp, 0, " (%s)",
|
|
(cgend != CORPSTAT_RANDOM) ? genders[mgend].adj
|
|
: "unspecified gender");
|
|
}
|
|
|
|
if ((obj->owornmask & W_WEP) && !gm.mrg_to_wielded) {
|
|
boolean twoweap_primary = (obj == uwep && u.twoweap),
|
|
tethered = (obj->otyp == AKLYS);
|
|
|
|
|
|
/* use alternate phrasing for non-weapons and for wielded ammo
|
|
(arrows, bolts), or missiles (darts, shuriken, boomerangs)
|
|
except when those are being actively dual-wielded where the
|
|
regular phrasing will list them as "in right hand" to
|
|
contrast with secondary weapon's "in left hand" */
|
|
if ((obj->quan != 1L
|
|
|| ((obj->oclass == WEAPON_CLASS)
|
|
? (is_ammo(obj) || is_missile(obj))
|
|
: !is_weptool(obj)))
|
|
&& !twoweap_primary) {
|
|
Concat(bp, 0, " (wielded)");
|
|
} else {
|
|
const char *hand_s = body_part(HAND);
|
|
char *obufp, handsbuf[40];
|
|
|
|
if (bimanual(obj)) { /* "hands" */
|
|
hand_s = strcpy(handsbuf, obufp = makeplural(hand_s));
|
|
releaseobuf(obufp);
|
|
} else { /* "right hand" or "left hand" */
|
|
Sprintf(handsbuf, "%s %s",
|
|
URIGHTY ? "right" : "left", hand_s);
|
|
hand_s = handsbuf;
|
|
}
|
|
/* note: Sting's glow message, if added, will insert text
|
|
in front of "(weapon in hand)"'s closing paren */
|
|
ConcatF2(bp, 0, " (%s %s)",
|
|
tethered ? "tethered to"
|
|
: twoweap_primary ? "wielded in"
|
|
: "weapon in",
|
|
hand_s);
|
|
|
|
/* we just added a parenthesized phrase, but the right paren
|
|
might be absent if the appended string got truncated */
|
|
if (!Blind && bpspaceleft && bp_eos[-1] == ')') {
|
|
if (gw.warn_obj_cnt && obj == uwep
|
|
&& (EWarn_of_mon & W_WEP) != 0L)
|
|
/* we know bp[] ends with ')'; overwrite that */
|
|
ConcatF2(bp, 1, ", %s %s)",
|
|
glow_verb(gw.warn_obj_cnt, TRUE),
|
|
glow_color(obj->oartifact));
|
|
else if (obj->lamplit && artifact_light(obj))
|
|
/* as above, overwrite known closing paren */
|
|
ConcatF1(bp, 1, ", %s lit)",
|
|
arti_light_description(obj));
|
|
}
|
|
}
|
|
}
|
|
if (obj->owornmask & W_SWAPWEP) {
|
|
if (u.twoweap)
|
|
ConcatF2(bp, 0, " (wielded in %s %s)",
|
|
URIGHTY ? "left" : "right", body_part(HAND));
|
|
else
|
|
/* TODO: rephrase this when obj isn't a weapon or weptool */
|
|
ConcatF1(bp, 0, " (alternate weapon%s; not wielded)",
|
|
plur(obj->quan));
|
|
}
|
|
if (obj->owornmask & W_QUIVER) {
|
|
int Qtyp;
|
|
|
|
switch (obj->oclass) {
|
|
case WEAPON_CLASS:
|
|
Qtyp = !is_ammo(obj) ? 3 /* not ammo: "at the ready" */
|
|
: (objects[obj->otyp].oc_skill != -P_BOW) ? 2 /* non-bow */
|
|
: 1; /* ammo for a bow: "in quiver" */
|
|
break;
|
|
case RING_CLASS:
|
|
case AMULET_CLASS:
|
|
case WAND_CLASS:
|
|
case COIN_CLASS:
|
|
case GEM_CLASS:
|
|
Qtyp = 2; /* small, non-bow: "in quiver pouch" */
|
|
break;
|
|
default: /* odd things */
|
|
Qtyp = 3; /* "at the ready" */
|
|
break;
|
|
}
|
|
ConcatF1(bp, 0, " (%s)",
|
|
(Qtyp == 1) ? "in quiver"
|
|
: (Qtyp == 2) ? "in quiver pouch"
|
|
: "at the ready");
|
|
}
|
|
|
|
/* treat 'restoring' like suppress_price because shopkeeper and
|
|
bill might not be available yet while restore is in progress
|
|
(objects won't normally be formatted during that time, but if
|
|
'perm_invent' is enabled then they might be [not any more...]) */
|
|
if (iflags.suppress_price || program_state.restoring) {
|
|
; /* don't attempt to obtain any shop pricing, even if 'with_price' */
|
|
} else if (is_unpaid(obj)) { /* in inventory or in container in invent */
|
|
char pricebuf[40];
|
|
long quotedprice = unpaid_cost(obj, COST_CONTENTS);
|
|
|
|
/* separately formatted suffix avoids need for ConcatF3() */
|
|
Sprintf(pricebuf, "%ld %s", quotedprice, currency(quotedprice));
|
|
ConcatF2(bp, 0, " (%s, %s)",
|
|
obj->unpaid ? "unpaid" : "contents", pricebuf);
|
|
} else if (with_price) { /* on floor or in container on floor */
|
|
int nochrg = 0;
|
|
long price = get_cost_of_shop_item(obj, &nochrg);
|
|
|
|
if (price > 0L) {
|
|
char pricebuf[40];
|
|
|
|
Sprintf(pricebuf, "%ld %s", price, currency(price));
|
|
ConcatF2(bp, 0, " (%s, %s)",
|
|
nochrg ? "contents" : "for sale", pricebuf);
|
|
} else if (nochrg > 0) {
|
|
Concat(bp, 0, " (no charge)");
|
|
}
|
|
}
|
|
|
|
if (!strncmp(prefix, "a ", 2)) {
|
|
/* save current prefix, without "a "; might be empty */
|
|
Strcpy(tmpbuf, prefix + 2);
|
|
/* set prefix[] to "", "a ", or "an " */
|
|
(void) just_an(prefix, *tmpbuf ? tmpbuf : bp);
|
|
/* append remainder of original prefix */
|
|
Strcat(prefix, tmpbuf);
|
|
}
|
|
|
|
/* show weight for items (debug tourist info);
|
|
"aum" is stolen from Crawl's "Arbitrary Unit of Measure" */
|
|
if (wizard && iflags.wizweight) {
|
|
/* wizard mode user has asked to see object weights */
|
|
if (with_price && bp_eos[-1] == ')')
|
|
ConcatF1(bp, 1, ", %u aum)", obj->owt);
|
|
else
|
|
ConcatF1(bp, 0, " (%u aum)", obj->owt);
|
|
|
|
/* ConcatF1(bp) updates bp_eos and bpspaceleft but we're done
|
|
with them now; add a fake use so compiler won't complain
|
|
about a variable assignment that won't be subsequently used */
|
|
nhUse(bp_eos);
|
|
nhUse(bpspaceleft);
|
|
}
|
|
|
|
bp = strprepend(bp, prefix);
|
|
|
|
/*
|
|
* Last gasp bounds check.
|
|
*
|
|
* If caller intends this to be for a menu entry, make sure that
|
|
* there is some room to combine with menu selector prefix without
|
|
* exceeding BUFSZ-1.
|
|
*
|
|
* offsetbp=4: width of menu entry selector text: "c - " for tty.
|
|
* For curses, that wastes a char since it only needs 3: "c) ".
|
|
*
|
|
* Reaching full BUFSZ-1 length can't happen unless both doname
|
|
* (BUFSZ-PREFIX) and strprepend (PREFIX) use up all available
|
|
* space or one of them overflows without being detected.
|
|
*/
|
|
if (strlen(bp) > BUFSZ - 1) {
|
|
paniclog("doname", bp);
|
|
/* ideally this will never happen; if xnamep is any obuf[]
|
|
other than the last, overflow here would be relatively
|
|
benign and we could probably keep going */
|
|
panic("doname: long object description overflow.");
|
|
/*NOTREACHED*/
|
|
} else {
|
|
static int doname_full = 0;
|
|
int offsetbp = for_menu ? 4 : 0;
|
|
|
|
if (strlen(bp) + offsetbp >= BUFSZ - 1) {
|
|
/* for !offsetbp, we'll only get here if strlen(bp)==BUFSZ-1 */
|
|
if (!doname_full++) {
|
|
paniclog("doname", bp);
|
|
Sprintf(tmpbuf, "long object description%s.",
|
|
offsetbp ? " truncated for menu use" : "");
|
|
paniclog("doname", tmpbuf);
|
|
}
|
|
bp[BUFSZ - 1 - offsetbp] = '\0';
|
|
}
|
|
}
|
|
|
|
return bp;
|
|
}
|
|
|
|
char *
|
|
doname(struct obj *obj)
|
|
{
|
|
return doname_base(obj, (unsigned) 0);
|
|
}
|
|
|
|
/* Name of object including price. */
|
|
char *
|
|
doname_with_price(struct obj *obj)
|
|
{
|
|
return doname_base(obj, DONAME_WITH_PRICE);
|
|
}
|
|
|
|
/* "some" instead of precise quantity if obj->dknown not set */
|
|
char *
|
|
doname_vague_quan(struct obj *obj)
|
|
{
|
|
/* Used by farlook.
|
|
* If it hasn't been seen up close and quantity is more than one,
|
|
* use "some" instead of the quantity: "some gold pieces" rather
|
|
* than "25 gold pieces". This is suboptimal, to put it mildly,
|
|
* because lookhere and pickup report the precise amount.
|
|
* Picking the item up while blind also shows the precise amount
|
|
* for inventory display, then dropping it while still blind leaves
|
|
* obj->dknown unset so the count reverts to "some" for farlook.
|
|
*
|
|
* TODO: add obj->qknown flag for 'quantity known' on stackable
|
|
* items; it could overlay obj->cknown since no containers stack.
|
|
*/
|
|
return doname_base(obj, DONAME_VAGUE_QUAN);
|
|
}
|
|
|
|
/* used from invent.c */
|
|
boolean
|
|
not_fully_identified(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_STRUCTURES
|
|
|| (!otmp->bknown && otmp->otyp != SCR_MAIL)
|
|
#else
|
|
|| !otmp->bknown
|
|
#endif
|
|
|| !objects[otmp->otyp].oc_name_known)
|
|
return TRUE;
|
|
if ((!otmp->cknown && (Is_container(otmp) || otmp->otyp == STATUE))
|
|
|| (!otmp->lknown && Is_box(otmp)))
|
|
return TRUE;
|
|
if (otmp->oartifact && undiscovered_artifact(otmp->oartifact))
|
|
return TRUE;
|
|
/* otmp->rknown is the only item of interest if we reach here */
|
|
/*
|
|
* Note: if a revision ever allows scrolls to become fireproof or
|
|
* rings to become shockproof, this checking will need to be revised.
|
|
* `rknown' ID only matters if xname() will provide the info about it.
|
|
*/
|
|
if (otmp->rknown
|
|
|| (otmp->oclass != ARMOR_CLASS && otmp->oclass != WEAPON_CLASS
|
|
&& !is_weptool(otmp) /* (redundant) */
|
|
&& otmp->oclass != BALL_CLASS)) /* (useless) */
|
|
return FALSE;
|
|
else /* lack of `rknown' only matters for vulnerable objects */
|
|
return (boolean) is_damageable(otmp);
|
|
}
|
|
|
|
/* format a corpse name (xname() omits monster type; doname() calls us);
|
|
eatcorpse() also uses us for death reason when eating tainted glob */
|
|
char *
|
|
corpse_xname(
|
|
struct obj *otmp,
|
|
const char *adjective,
|
|
unsigned cxn_flags) /* bitmask of CXN_xxx values */
|
|
{
|
|
char *nambuf;
|
|
int omndx = otmp->corpsenm;
|
|
boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0,
|
|
/* suppress "the" from "the unique monster corpse" */
|
|
no_prefix = (cxn_flags & CXN_NO_PFX) != 0,
|
|
/* include "the" for "the woodchuck corpse */
|
|
the_prefix = (cxn_flags & CXN_PFX_THE) != 0,
|
|
/* include "an" for "an ogre corpse */
|
|
any_prefix = (cxn_flags & CXN_ARTICLE) != 0,
|
|
/* leave off suffix (do_name() appends "corpse" itself) */
|
|
omit_corpse = (cxn_flags & CXN_NOCORPSE) != 0,
|
|
possessive = FALSE,
|
|
glob = (otmp->otyp != CORPSE && otmp->globby);
|
|
const char *mnam;
|
|
|
|
/* some callers [aobjnam()] rely on prefix area that xname() sets aside */
|
|
gx.xnamep = nextobuf();
|
|
nambuf = gx.xnamep + PREFIX;
|
|
|
|
if (glob) {
|
|
mnam = OBJ_NAME(objects[otmp->otyp]); /* "glob of <monster>" */
|
|
} else if (omndx == NON_PM) { /* paranoia */
|
|
mnam = "thing";
|
|
} else {
|
|
mnam = obj_pmname(otmp);
|
|
if (the_unique_pm(&mons[omndx]) || type_is_pname(&mons[omndx])) {
|
|
mnam = s_suffix(mnam);
|
|
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 ");
|
|
/* note: over time, various instances of the(mon_name()) have crept
|
|
into the code, so the() has been modified to deal with capitalized
|
|
monster names; we could switch to using it below like an() */
|
|
|
|
if (!adjective || !*adjective) {
|
|
/* normal case: newt corpse */
|
|
Strcat(nambuf, mnam);
|
|
} else {
|
|
/* adjective positioning depends upon format of monster name */
|
|
if (possessive) /* Medusa's cursed partly eaten corpse */
|
|
Sprintf(eos(nambuf), "%s %s", mnam, adjective);
|
|
else /* cursed partly eaten troll corpse */
|
|
Sprintf(eos(nambuf), "%s %s", adjective, mnam);
|
|
/* in case adjective has a trailing space, squeeze it out */
|
|
mungspaces(nambuf);
|
|
/* doname() might include a count in the adjective argument;
|
|
if so, don't prepend an article */
|
|
if (digit(*adjective))
|
|
any_prefix = FALSE;
|
|
}
|
|
|
|
if (glob) {
|
|
; /* omit_corpse doesn't apply; quantity is always 1 */
|
|
} else if (!omit_corpse) {
|
|
Strcat(nambuf, " corpse");
|
|
/* makeplural(nambuf) => append "s" to "corpse" */
|
|
if (otmp->quan > 1L && !ignore_quan) {
|
|
Strcat(nambuf, "s");
|
|
any_prefix = FALSE; /* avoid "a newt corpses" */
|
|
}
|
|
}
|
|
|
|
/* it's safe to overwrite our nambuf[] after an() has copied its
|
|
old value into another buffer; and once _that_ has been copied,
|
|
the obuf[] returned by an() can be made available for re-use */
|
|
if (any_prefix) {
|
|
char *obufp;
|
|
|
|
Strcpy(nambuf, obufp = an(nambuf));
|
|
releaseobuf(obufp);
|
|
}
|
|
return nambuf;
|
|
}
|
|
|
|
/* xname doesn't include monster type for "corpse"; cxname does */
|
|
char *
|
|
cxname(struct obj *obj)
|
|
{
|
|
if (obj->otyp == CORPSE)
|
|
return corpse_xname(obj, (const char *) 0, CXN_NORMAL);
|
|
return xname(obj);
|
|
}
|
|
|
|
/* like cxname, but ignores quantity */
|
|
char *
|
|
cxname_singular(struct obj *obj)
|
|
{
|
|
if (obj->otyp == CORPSE)
|
|
return corpse_xname(obj, (const char *) 0, CXN_SINGULAR);
|
|
return xname_flags(obj, CXN_SINGULAR);
|
|
}
|
|
|
|
/* treat an object as fully ID'd when it might be used as reason for death */
|
|
char *
|
|
killer_xname(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 = 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(
|
|
struct obj *obj,
|
|
char *(*func)(OBJ_P), /* main formatting routine */
|
|
char *(*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(struct obj *otmp, char *(*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;
|
|
}
|
|
|
|
/* pick "", "a ", or "an " as article for 'str'; used by an() and doname() */
|
|
char *
|
|
just_an(char *outbuf, const char *str)
|
|
{
|
|
char c0;
|
|
|
|
*outbuf = '\0';
|
|
c0 = lowc(*str);
|
|
if (!str[1] || str[1] == ' ') {
|
|
/* single letter; might be used for named fruit or a musical note */
|
|
Strcpy(outbuf, strchr("aefhilmnosx", c0) ? "an " : "a ");
|
|
} else if (!strncmpi(str, "the ", 4)
|
|
/* these probably shouldn't be handled here because doing so
|
|
impacts inventory when using them for named fruit */
|
|
|| !strcmpi(str, "molten lava")
|
|
|| !strcmpi(str, "iron bars")
|
|
|| !strcmpi(str, "ice")
|
|
) {
|
|
; /* no article */
|
|
} else {
|
|
/* normal case is "an <vowel>" or "a <consonant>" */
|
|
if ((strchr(vowels, c0) /* some exceptions warranting "a <vowel>" */
|
|
/* 'wun' initial sound */
|
|
&& (strncmpi(str, "one", 3) || (str[3] && !strchr("-_ ", str[3])))
|
|
/* long 'u' initial sound */
|
|
&& strncmpi(str, "eu", 2) /* "eucalyptus leaf" */
|
|
&& strncmpi(str, "uke", 3) && strncmpi(str, "ukulele", 7)
|
|
&& strncmpi(str, "unicorn", 7) && strncmpi(str, "uranium", 7)
|
|
&& strncmpi(str, "useful", 6)) /* "useful tool" */
|
|
|| (c0 == 'x' && !strchr(vowels, lowc(str[1]))))
|
|
Strcpy(outbuf, "an ");
|
|
else
|
|
Strcpy(outbuf, "a ");
|
|
}
|
|
return outbuf;
|
|
}
|
|
|
|
char *
|
|
an(const char *str)
|
|
{
|
|
char *buf = nextobuf();
|
|
|
|
if (!str || !*str) {
|
|
impossible("Alphabet soup: 'an(%s)'.", str ? "\"\"" : "<null>");
|
|
return strcpy(buf, "an []");
|
|
}
|
|
(void) just_an(buf, str);
|
|
return strncat(buf, str, BUFSZ - 1 - Strlen(buf));
|
|
}
|
|
|
|
char *
|
|
An(const char *str)
|
|
{
|
|
char *tmp = an(str);
|
|
|
|
*tmp = highc(*tmp);
|
|
return tmp;
|
|
}
|
|
|
|
/*
|
|
* Prepend "the" if necessary; assumes str is a subject derived from xname.
|
|
* Use type_is_pname() for monster names, not the(). the() is idempotent.
|
|
*/
|
|
char *
|
|
the(const char *str)
|
|
{
|
|
const char *aname;
|
|
char *buf = nextobuf();
|
|
boolean insert_the = FALSE;
|
|
|
|
if (!str || !*str) {
|
|
impossible("Alphabet soup: 'the(%s)'.", str ? "\"\"" : "<null>");
|
|
return strcpy(buf, "the []");
|
|
}
|
|
if (!strncmpi(str, "the ", 4)) {
|
|
buf[0] = lowc(*str);
|
|
Strcpy(&buf[1], str + 1);
|
|
return buf;
|
|
} else if (*str < 'A' || *str > 'Z'
|
|
/* some capitalized monster names want "the", others don't */
|
|
|| CapitalMon(str)
|
|
/* treat named fruit as not a proper name, even if player
|
|
has assigned a capitalized proper name as his/her fruit,
|
|
unless it matches an artifact name */
|
|
|| (fruit_from_name(str, TRUE, (int *) 0)
|
|
&& ((aname = artifact_name(str, (short *) 0, FALSE)) == 0
|
|
|| strncmpi(aname, "the ", 4) == 0))) {
|
|
/* not a proper name, needs an article */
|
|
insert_the = TRUE;
|
|
} else {
|
|
/* Probably a proper name, might not need an article */
|
|
char *tmp, *named, *called;
|
|
int l;
|
|
|
|
/* some objects have capitalized adjectives in their names */
|
|
if (((tmp = strrchr(str, ' ')) != 0 || (tmp = strrchr(str, '-')) != 0)
|
|
&& (tmp[1] < 'A' || tmp[1] > 'Z')) {
|
|
/* insert "the" unless we have an apostrophe (where we assume
|
|
we're dealing with "Unique's corpse" when "Unique" wasn't
|
|
caught by CapitalMon() above) */
|
|
insert_the = !strchr(str, '\'');
|
|
} else if (tmp && strchr(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';
|
|
return strncat(buf, str, BUFSZ - 1 - Strlen(buf));
|
|
}
|
|
|
|
char *
|
|
The(const char *str)
|
|
{
|
|
char *tmp = the(str);
|
|
|
|
*tmp = highc(*tmp);
|
|
return tmp;
|
|
}
|
|
|
|
/* returns "count cxname(otmp)" or just cxname(otmp) if count == 1 */
|
|
char *
|
|
aobjnam(struct obj *otmp, const char *verb)
|
|
{
|
|
char prefix[PREFIX];
|
|
char *bp = cxname(otmp);
|
|
|
|
if (otmp->quan != 1L) {
|
|
Sprintf(prefix, "%ld ", otmp->quan);
|
|
bp = strprepend(bp, prefix);
|
|
}
|
|
if (verb) {
|
|
Strcat(bp, " ");
|
|
Strcat(bp, otense(otmp, verb));
|
|
}
|
|
return bp;
|
|
}
|
|
|
|
/* combine yname and aobjnam eg "your count cxname(otmp)" */
|
|
char *
|
|
yobjnam(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(struct obj *obj, const char *verb)
|
|
{
|
|
char *s = yobjnam(obj, verb);
|
|
|
|
*s = highc(*s);
|
|
return s;
|
|
}
|
|
|
|
/* like aobjnam, but prepend "The", not count, and use xname */
|
|
char *
|
|
Tobjnam(struct obj *otmp, const char *verb)
|
|
{
|
|
char *bp = The(xname(otmp));
|
|
|
|
if (verb) {
|
|
Strcat(bp, " ");
|
|
Strcat(bp, otense(otmp, verb));
|
|
}
|
|
return bp;
|
|
}
|
|
|
|
/* capitalized variant of doname() */
|
|
char *
|
|
Doname2(struct obj *obj)
|
|
{
|
|
char *s = doname(obj);
|
|
|
|
*s = highc(*s);
|
|
return s;
|
|
}
|
|
|
|
/* doname() for itemized buying of 'obj' from a shop */
|
|
char *
|
|
paydoname(struct obj *obj)
|
|
{
|
|
static const char and_contents[] = " and its contents";
|
|
char *p;
|
|
unsigned save_cknown = obj->cknown;
|
|
boolean save_wizweight = iflags.wizweight;
|
|
|
|
if (Has_contents(obj))
|
|
obj->cknown = 0;
|
|
/* avoid showing item weights to unclutter billing's pay-menu a bit */
|
|
iflags.wizweight = FALSE;
|
|
/* suppress invent-style price; caller will add billing-style price */
|
|
iflags.suppress_price++;
|
|
p = doname_base(obj, 0U);
|
|
iflags.suppress_price--;
|
|
iflags.wizweight = save_wizweight;
|
|
|
|
if (Has_contents(obj)) {
|
|
/* buy_container() sets no_charge for a container that has just
|
|
been purchased so that when paydoname() is called by
|
|
shk_names_obj(), we'll provide "a/an <container>" instead of
|
|
"your <container>" */
|
|
if (!obj->no_charge) {
|
|
if (!strncmp(p, "a ", 2))
|
|
p += 2;
|
|
else if (!strncmp(p, "an ", 3))
|
|
p += 3;
|
|
p = strprepend(p, obj->unpaid ? "an unpaid " : "your ");
|
|
}
|
|
|
|
if (!obj->cknown) {
|
|
if (obj->unpaid) {
|
|
if ((int) strlen(p) + sizeof and_contents - 1
|
|
< BUFSZ - PREFIX)
|
|
Strcat(p, and_contents);
|
|
} else {
|
|
p = strprepend(p, "the contents of ");
|
|
}
|
|
}
|
|
}
|
|
obj->cknown = save_cknown;
|
|
return p;
|
|
}
|
|
|
|
/* returns "[your ]xname(obj)" or "Foobar's xname(obj)" or "the xname(obj)" */
|
|
char *
|
|
yname(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(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(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(struct obj *obj)
|
|
{
|
|
char *s = ysimple_name(obj);
|
|
|
|
*s = highc(*s);
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* FIXME:
|
|
* simpleonames(), ansimpleoname(), and thesimpleoname() need to
|
|
* know the beginning of the obuf[] they use so that they can
|
|
* guard against buffer overflow when pluralizing (is that an
|
|
* actual word?) or inserting "an" or "the".
|
|
*
|
|
* minimal_xname() returns a call to xname() which writes into
|
|
* the middle of its obuf[] then backs up to accomodate a prefix,
|
|
* so BUFSZ is not a reliable limit for the length of the result.
|
|
*
|
|
* [Overflow likely moot. Since the formatted object name has
|
|
* user-supplied name suppressed, the length is sure to be short
|
|
* enough to added plural suffix or "an" or "the" prefix.]
|
|
*/
|
|
|
|
/* "scroll" or "scrolls" */
|
|
char *
|
|
simpleonames(struct obj *obj)
|
|
{
|
|
char *obufp, *simpleoname = minimal_xname(obj);
|
|
|
|
if (obj->quan != 1L) {
|
|
/* 'simpleoname' points to an obuf; makeplural() will allocate
|
|
another one and only that one can be explicitly released for
|
|
re-use, so this is slightly convoluted to cope with that;
|
|
makeplural() will be fully evaluated and done with its input
|
|
argument before strcpy() touches its output argument */
|
|
Strcpy(simpleoname, obufp = makeplural(simpleoname));
|
|
releaseobuf(obufp);
|
|
}
|
|
return simpleoname;
|
|
}
|
|
|
|
/* "a scroll" or "scrolls"; "a silver bell" or "the Bell of Opening" */
|
|
char *
|
|
ansimpleoname(struct obj *obj)
|
|
{
|
|
char *obufp, *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 minimal_xname() handle
|
|
any `known' and `dknown' checking necessary) */
|
|
if (otyp == FAKE_AMULET_OF_YENDOR)
|
|
otyp = AMULET_OF_YENDOR;
|
|
if (objects[otyp].oc_unique && OBJ_NAME(objects[otyp])
|
|
&& !strcmp(simpleoname, OBJ_NAME(objects[otyp]))) {
|
|
/* the() will allocate another obuf[]; we want to avoid using two */
|
|
obufp = the(simpleoname);
|
|
Strcpy(simpleoname, obufp);
|
|
releaseobuf(obufp);
|
|
} else if (obj->quan == 1L) {
|
|
/* simpleoname[] is singular if quan==1, plural otherwise;
|
|
an() will allocate another obuf[]; we want to avoid using two */
|
|
obufp = an(simpleoname);
|
|
Strcpy(simpleoname, obufp);
|
|
releaseobuf(obufp);
|
|
}
|
|
return simpleoname;
|
|
}
|
|
|
|
/* "the scroll" or "the scrolls" */
|
|
char *
|
|
thesimpleoname(struct obj *obj)
|
|
{
|
|
char *obufp, *simpleoname = simpleonames(obj);
|
|
|
|
/* the() will allocate another obuf[]; we want to avoid using two */
|
|
obufp = the(simpleoname);
|
|
Strcpy(simpleoname, obufp);
|
|
releaseobuf(obufp);
|
|
return simpleoname;
|
|
}
|
|
|
|
/* basic name of obj, as if it has been discovered; for some types of
|
|
items, we can't just use OBJ_NAME() because it doesn't always include
|
|
the class (for instance "light" when we want "spellbook of light");
|
|
minimal_xname() uses xname() to get that */
|
|
char *
|
|
actualoname(struct obj *obj)
|
|
{
|
|
char *res;
|
|
|
|
iflags.override_ID = TRUE;
|
|
res = minimal_xname(obj);
|
|
iflags.override_ID = FALSE;
|
|
return res;
|
|
}
|
|
|
|
/* artifact's name without any object type or known/dknown/&c feedback */
|
|
char *
|
|
bare_artifactname(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 *const 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(struct obj *otmp, const char *verb)
|
|
{
|
|
char *buf;
|
|
|
|
/*
|
|
* verb is given in plural (without trailing s). Return as input
|
|
* if the result of xname(otmp) would be plural. Don't bother
|
|
* recomputing xname(otmp) at this time.
|
|
*/
|
|
if (!is_plural(otmp))
|
|
return vtense((char *) 0, verb);
|
|
|
|
buf = nextobuf();
|
|
Strcpy(buf, verb);
|
|
return buf;
|
|
}
|
|
|
|
/* various singular words that vtense would otherwise categorize as plural;
|
|
also used by makesingular() to catch some special cases */
|
|
static const char *const special_subjs[] = {
|
|
"erinys", "manes", /* this one is ambiguous */
|
|
"Cyclops", "Hippocrates", "Pelias", "aklys",
|
|
"amnesia", "detect monsters", "paralysis", "shape changers",
|
|
"nemesis", 0
|
|
/* note: "detect monsters" and "shape changers" are normally
|
|
caught via "<something>(s) of <whatever>", but they can be
|
|
wished for using the shorter form, so we include them here
|
|
to accommodate usage by makesingular during wishing */
|
|
};
|
|
|
|
/* return form of the verb (input plural) for present tense 3rd person subj */
|
|
char *
|
|
vtense(const char *subj, 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 = strchr(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
|
|
&& !strchr("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 (strchr("zxs", lowc(*bspot))
|
|
|| (len >= 2 && lowc(*bspot) == 'h'
|
|
&& strchr("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' && !strchr(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 const struct sing_plur one_off[] = {
|
|
{ "child",
|
|
"children" }, /* (for wise guys who give their food funny names) */
|
|
{ "cubus", "cubi" }, /* in-/suc-cubus */
|
|
{ "culus", "culi" }, /* homunculus */
|
|
{ "Cyclops", "Cyclopes" },
|
|
{ "djinni", "djinn" },
|
|
{ "erinys", "erinyes" },
|
|
{ "foot", "feet" },
|
|
{ "fungus", "fungi" },
|
|
{ "goose", "geese" },
|
|
{ "knife", "knives" },
|
|
{ "labrum", "labra" }, /* candelabrum */
|
|
{ "louse", "lice" },
|
|
{ "mouse", "mice" },
|
|
{ "mumak", "mumakil" },
|
|
{ "nemesis", "nemeses" },
|
|
{ "ovum", "ova" },
|
|
{ "ox", "oxen" },
|
|
{ "passerby", "passersby" },
|
|
{ "rtex", "rtices" }, /* vortex */
|
|
{ "serum", "sera" },
|
|
{ "staff", "staves" },
|
|
{ "tooth", "teeth" },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static const char *const as_is[] = {
|
|
/* makesingular() leaves these plural due to how they're used */
|
|
"boots", "shoes", "gloves", "lenses", "scales",
|
|
"eyes", "gauntlets", "iron bars",
|
|
/* both singular and plural are spelled the same */
|
|
"bison", "deer", "elk", "fish", "fowl",
|
|
"tuna", "yaki", "-hai", "krill", "manes",
|
|
"moose", "ninja", "sheep", "ronin", "roshi",
|
|
"shito", "tengu", "ki-rin", "Nazgul", "gunyoki",
|
|
"piranha", "samurai", "shuriken", "haggis", "Bordeaux",
|
|
0,
|
|
/* Note: "fish" and "piranha" are collective plurals, suitable
|
|
for "wiped out all <foo>". For "3 <foo>", they should be
|
|
"fishes" and "piranhas" instead. We settle for collective
|
|
variant instead of attempting to support both. */
|
|
};
|
|
|
|
/* singularize/pluralize decisions common to both makesingular & makeplural */
|
|
staticfn boolean
|
|
singplur_lookup(
|
|
char *basestr, char *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;
|
|
int baselen = Strlen(basestr);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Leave "craft" as a suffix as-is (aircraft, hovercraft);
|
|
"craft" itself is (arguably) not included in our likely context */
|
|
if ((baselen > 5) && (!BSTRCMPI(basestr, endstring - 5, "craft")))
|
|
return TRUE;
|
|
/* avoid false hit on one_off[].plur == "lice" or .sing == "goose";
|
|
if more of these turn up, one_off[] entries will need to flagged
|
|
as to which are whole words and which are matchable as suffices
|
|
then matching in the loop below will end up becoming more complex */
|
|
if (!strcmpi(basestr, "slice")
|
|
|| !strcmpi(basestr, "mongoose")) {
|
|
if (to_plural)
|
|
Strcasecpy(endstring, "s");
|
|
return TRUE;
|
|
}
|
|
/* skip "ox" -> "oxen" entry when pluralizing "<something>ox"
|
|
unless it is muskox */
|
|
if (to_plural && baselen > 2 && !strcmpi(endstring - 2, "ox")
|
|
&& !(baselen > 5 && !strcmpi(endstring - 6, "muskox"))) {
|
|
/* "fox" -> "foxes" */
|
|
Strcasecpy(endstring, "es");
|
|
return TRUE;
|
|
}
|
|
if (to_plural) {
|
|
if (baselen > 2 && !strcmpi(endstring - 3, "man")
|
|
&& badman(basestr, to_plural)) {
|
|
Strcasecpy(endstring, "s");
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
if (baselen > 2 && !strcmpi(endstring - 3, "men")
|
|
&& badman(basestr, to_plural))
|
|
return TRUE;
|
|
}
|
|
for (sp = one_off; sp->sing; sp++) {
|
|
/* check whether endstring already matches */
|
|
same = to_plural ? sp->plur : sp->sing;
|
|
al = (int) strlen(same);
|
|
if (!BSTRCMPI(basestr, endstring - al, same))
|
|
return TRUE; /* use as-is */
|
|
/* check whether it matches the inverse; if so, transform it */
|
|
other = to_plural ? sp->sing : sp->plur;
|
|
al = (int) strlen(other);
|
|
if (!BSTRCMPI(basestr, endstring - al, other)) {
|
|
Strcasecpy(endstring - al, same);
|
|
return TRUE; /* one_off[] transformation */
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* searches for common compounds, ex. lump of royal jelly */
|
|
staticfn char *
|
|
singplur_compound(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 ",
|
|
" au ", "-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 (!strchr(compound_start, *p))
|
|
continue;
|
|
|
|
/* check current substring against all words in the compound[] list */
|
|
for (cmpd = compounds; *cmpd; ++cmpd)
|
|
if (!strncmpi(p, *cmpd, (int) strlen(*cmpd)))
|
|
return p;
|
|
}
|
|
/* wasn't recognized as a compound phrase */
|
|
return 0;
|
|
}
|
|
|
|
/* Plural routine; once upon a time it may have been chiefly used for
|
|
* user-defined fruits, but it is now used extensively throughout the
|
|
* program.
|
|
*
|
|
* For fruit, we have to try to account for everything reasonable the
|
|
* player has; something unreasonable can still break the code.
|
|
* However, it's still a lot more accurate than "just add an 's' at the
|
|
* end", which Rogue uses...
|
|
*
|
|
* Also used for plural monster names ("Wiped out all homunculi." or the
|
|
* vanquished monsters list) and body parts. A lot of unique monsters have
|
|
* names which get mangled by makeplural and/or makesingular. They're not
|
|
* genocidable, and vanquished-mon handling does its own special casing
|
|
* (for uniques who've been revived and re-killed), so we don't bother
|
|
* trying to get those right here.
|
|
*
|
|
* Also misused by muse.c to convert 1st person present verbs to 2nd person.
|
|
* 3.6.0: made case-insensitive.
|
|
*/
|
|
char *
|
|
makeplural(const char *oldstr)
|
|
{
|
|
char *spot;
|
|
char lo_c, *str = nextobuf();
|
|
const char *excess = (char *) 0;
|
|
int len, i;
|
|
|
|
if (oldstr)
|
|
while (*oldstr == ' ')
|
|
oldstr++;
|
|
if (!oldstr || !*oldstr) {
|
|
impossible("plural of null?");
|
|
Strcpy(str, "s");
|
|
return str;
|
|
}
|
|
/* makeplural() is sometimes used on monsters rather than objects
|
|
and sometimes pronouns are used for monsters, so check those;
|
|
unfortunately, "her" (which matches genders[1].him and [1].his)
|
|
and "it" (which matches genders[2].he and [2].him) are ambiguous;
|
|
we'll live with that; caller can fix things up if necessary */
|
|
*str = '\0';
|
|
for (i = 0; i <= 2; ++i) {
|
|
if (!strcmpi(genders[i].he, oldstr))
|
|
Strcpy(str, genders[3].he); /* "they" */
|
|
else if (!strcmpi(genders[i].him, oldstr))
|
|
Strcpy(str, genders[3].him); /* "them" */
|
|
else if (!strcmpi(genders[i].his, oldstr))
|
|
Strcpy(str, genders[3].his); /* "their" */
|
|
if (*str) {
|
|
if (oldstr[0] == highc(oldstr[0]))
|
|
str[0] = highc(str[0]);
|
|
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 */
|
|
"eaux", /* chateaux, gateaux */
|
|
"matzot", 0,
|
|
};
|
|
|
|
/* spot+1: synch up with makesingular's usage */
|
|
if (singplur_lookup(str, spot + 1, TRUE, already_plural))
|
|
goto bottom;
|
|
|
|
/* more of same, but not suitable for blanket loop checking */
|
|
if ((len == 2 && !strcmpi(str, "ya"))
|
|
|| (len >= 3 && !strcmpi(spot - 2, " ya")))
|
|
goto bottom;
|
|
}
|
|
|
|
/* man/men ("Wiped out all cavemen.") */
|
|
if (len >= 3 && !strcmpi(spot - 2, "man")
|
|
/* exclude shamans and humans etc */
|
|
&& !badman(str, TRUE)) {
|
|
Strcasecpy(spot - 1, "en");
|
|
goto bottom;
|
|
}
|
|
if (lowc(*spot) == 'f') { /* (staff handled via one_off[]) */
|
|
lo_c = lowc(*(spot - 1));
|
|
if (len >= 3 && !strcmpi(spot - 2, "erf")) {
|
|
/* avoid "nerf" -> "nerves", "serf" -> "serves" */
|
|
; /* fall through to default (append 's') */
|
|
} else if (strchr("lr", lo_c) || strchr(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;
|
|
}
|
|
/* -eau/-eaux (gateau, chapeau...) */
|
|
if (len >= 3 && !strcmpi(spot - 2, "eau")
|
|
/* 'bureaus' is the more common plural of 'bureau' */
|
|
&& BSTRCMPI(str, spot - 5, "bureau")) {
|
|
Strcasecpy(spot + 1, "x");
|
|
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: ox/oxen, VAX/VAXen, goose/geese */
|
|
|
|
lo_c = lowc(*spot);
|
|
|
|
/* codex/spadix/neocortex and the like */
|
|
if (len >= 5
|
|
&& (!strcmpi(spot - 2, "dex")
|
|
||!strcmpi(spot - 2, "dix")
|
|
||!strcmpi(spot - 2, "tex"))
|
|
/* indices would have been ok too, but stick with indexes */
|
|
&& (strcmpi(spot - 4,"index") != 0)) {
|
|
Strcasecpy(spot - 1, "ices"); /* ex|ix -> ices */
|
|
goto bottom;
|
|
}
|
|
/* Ends in z, x, s, ch, sh; add an "es" */
|
|
if (strchr("zxs", lo_c)
|
|
|| (len >= 2 && lo_c == 'h' && strchr("cs", lowc(*(spot - 1)))
|
|
/* 21st century k-sound */
|
|
&& !(len >= 4 && lowc(*(spot - 1)) == 'c' && ch_ksound(str)))
|
|
/* 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' && !strchr(vowels, lowc(*(spot - 1)))) {
|
|
Strcasecpy(spot, "ies"); /* y -> ies */
|
|
goto bottom;
|
|
}
|
|
/* Default: append an 's' */
|
|
Strcasecpy(spot + 1, "s");
|
|
|
|
bottom:
|
|
if (excess)
|
|
Strcat(str, excess);
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Singularize a string the user typed in; this helps reduce the complexity
|
|
* of readobjnam, and is also used in pager.c to singularize the string
|
|
* for which help is sought.
|
|
*
|
|
* "Manes" is ambiguous: monster type (keep s), or horse body part (drop s)?
|
|
* Its inclusion in as_is[]/special_subj[] makes it get treated as the former.
|
|
*
|
|
* A lot of unique monsters have names ending in s; plural, or singular
|
|
* from plural, doesn't make much sense for them so we don't bother trying.
|
|
* 3.6.0: made case-insensitive.
|
|
*/
|
|
char *
|
|
makesingular(const char *oldstr)
|
|
{
|
|
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;
|
|
}
|
|
/* makeplural() of pronouns isn't reversible but at least we can
|
|
force a singular value */
|
|
*str = '\0';
|
|
if (!strcmpi(genders[3].he, oldstr)) /* "they" */
|
|
Strcpy(str, genders[2].he); /* "it" */
|
|
else if (!strcmpi(genders[3].him, oldstr)) /* "them" */
|
|
Strcpy(str, genders[2].him); /* also "it" */
|
|
else if (!strcmpi(genders[3].his, oldstr)) /* "their" */
|
|
Strcpy(str, genders[2].his); /* "its" */
|
|
if (*str) {
|
|
if (oldstr[0] == highc(oldstr[0]))
|
|
str[0] = highc(str[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")
|
|
/* avoid false match for "harpies" */
|
|
&& (p - 4 == bp || p[-5] == ' '))
|
|
/* alternate djinni/djinn spelling; not really needed */
|
|
|| (!BSTRCMPI(bp, p - 6, "genies")
|
|
/* avoid false match for "progenies" */
|
|
&& (p - 6 == bp || p[-7] == ' '))
|
|
|| !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 && (strchr("lr", lowc(*(p - 4)))
|
|
|| strchr(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, "shes") /* splashes [of venom] */
|
|
|| !BSTRCMPI(bp, p - 4, "sses") /* priestesses */
|
|
|| !BSTRCMPI(bp, p - 5, "atoes") /* tomatoes */
|
|
|| !BSTRCMPI(bp, p - 7, "dingoes")
|
|
|| !BSTRCMPI(bp, p - 7, "Aleaxes")) {
|
|
*(p - 2) = '\0'; /* drop es */
|
|
goto bottom;
|
|
} /* else fall through to mins */
|
|
|
|
/* ends in 's' but not 'es' */
|
|
} else if (!BSTRCMPI(bp, p - 2, "us")) { /* lotus, fungus... */
|
|
if (BSTRCMPI(bp, p - 6, "tengus") /* but not these... */
|
|
&& BSTRCMPI(bp, p - 7, "hezrous"))
|
|
goto bottom;
|
|
} else if (!BSTRCMPI(bp, p - 2, "ss")
|
|
|| !BSTRCMPI(bp, p - 5, " lens")
|
|
|| (p - 4 == bp && !strcmpi(p - 4, "lens"))) {
|
|
goto bottom;
|
|
}
|
|
mins:
|
|
*(p - 1) = '\0'; /* drop s */
|
|
|
|
} else { /* input doesn't end in 's' */
|
|
|
|
if (!BSTRCMPI(bp, p - 3, "men")
|
|
&& !badman(bp, FALSE)) {
|
|
Strcasecpy(p - 2, "an");
|
|
goto bottom;
|
|
}
|
|
/* matzot -> matzo, algae -> alga */
|
|
if (!BSTRCMPI(bp, p - 6, "matzot") || !BSTRCMPI(bp, p - 2, "ae")
|
|
|| !BSTRCMPI(bp, p - 4, "eaux")) {
|
|
*(p - 1) = '\0'; /* drop t/e/x */
|
|
goto bottom;
|
|
}
|
|
/* balactheria -> balactherium */
|
|
if (p - 4 >= bp && !strcmpi(p - 2, "ia")
|
|
&& strchr("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;
|
|
}
|
|
|
|
|
|
staticfn boolean
|
|
ch_ksound(const char *basestr)
|
|
{
|
|
/* these are some *ch words/suffixes that make a k-sound. They pluralize by
|
|
adding 's' rather than 'es' */
|
|
static const char *const ch_k[] = {
|
|
"monarch", "poch", "tech", "mech", "stomach", "psych",
|
|
"amphibrach", "anarch", "atriarch", "azedarach", "broch",
|
|
"gastrotrich", "isopach", "loch", "oligarch", "peritrich",
|
|
"sandarach", "sumach", "symposiarch",
|
|
};
|
|
int i, al;
|
|
const char *endstr;
|
|
|
|
if (!basestr || strlen(basestr) < 4)
|
|
return FALSE;
|
|
|
|
endstr = eos((char *) basestr);
|
|
for (i = 0; i < SIZE(ch_k); i++) {
|
|
al = (int) strlen(ch_k[i]);
|
|
if (!BSTRCMPI(basestr, endstr - al, ch_k[i]))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
staticfn boolean
|
|
badman(
|
|
const char *basestr,
|
|
boolean to_plural) /* True: makeplural, False: makesingular */
|
|
{
|
|
/* these are all the prefixes for *man that don't have a *men plural */
|
|
static const char *const no_men[] = {
|
|
"albu", "antihu", "anti", "ata", "auto", "bildungsro", "cai", "cay",
|
|
"ceru", "corner", "decu", "des", "dura", "fir", "hanu", "het",
|
|
"infrahu", "inhu", "nonhu", "otto", "out", "prehu", "protohu",
|
|
"subhu", "superhu", "talis", "unhu", "sha",
|
|
"hu", "un", "le", "re", "so", "to", "at", "a",
|
|
};
|
|
/* these are all the prefixes for *men that don't have a *man singular */
|
|
static const char *const no_man[] = {
|
|
"abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava",
|
|
"hegu", "preno", "sonar", "speci", "dai", "exa", "fla", "sta", "teg",
|
|
"tegu", "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi",
|
|
"ya", "o", "a",
|
|
};
|
|
int i, al;
|
|
const char *endstr, *spot;
|
|
|
|
if (!basestr || strlen(basestr) < 4)
|
|
return FALSE;
|
|
|
|
endstr = eos((char *) basestr);
|
|
|
|
if (to_plural) {
|
|
for (i = 0; i < SIZE(no_men); i++) {
|
|
al = (int) strlen(no_men[i]);
|
|
spot = endstr - (al + 3);
|
|
if (!BSTRNCMPI(basestr, spot, no_men[i], al)
|
|
&& (spot == basestr || *(spot - 1) == ' '))
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
for (i = 0; i < SIZE(no_man); i++) {
|
|
al = (int) strlen(no_man[i]);
|
|
spot = endstr - (al + 3);
|
|
if (!BSTRNCMPI(basestr, spot, no_man[i], al)
|
|
&& (spot == basestr || *(spot - 1) == ' '))
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* compare user string against object name string using fuzzy matching */
|
|
staticfn boolean
|
|
wishymatch(
|
|
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);
|
|
copynchars(eos(strcat(buf, " ")), u_str, (int) (u_of - u_str));
|
|
if (fuzzymatch(buf, o_str, " -", TRUE))
|
|
return TRUE;
|
|
} else if (o_of && !u_of) {
|
|
Strcpy(buf, o_of + 4);
|
|
copynchars(eos(strcat(buf, " ")), o_str, (int) (o_of - o_str));
|
|
if (fuzzymatch(u_str, buf, " -", TRUE))
|
|
return 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 (strstri(o_str, "helm") && strstri(u_str, "helmet")) {
|
|
copynchars(buf, u_str, (int) sizeof buf - 1);
|
|
(void) strsubst(buf, "helmet", "helm");
|
|
return wishymatch(buf, o_str, TRUE);
|
|
} else if (strstri(o_str, "gauntlets") && strstri(u_str, "gloves")) {
|
|
/* -3: room to replace shorter "gloves" with longer "gauntlets" */
|
|
copynchars(buf, u_str, (int) sizeof buf - 1 - 3);
|
|
(void) strsubst(buf, "gloves", "gauntlets");
|
|
return wishymatch(buf, o_str, TRUE);
|
|
} else if (!strncmp(o_str, detect_SP, sizeof detect_SP - 1)) {
|
|
/* check for "detect <foo>" vs "<foo> detection" */
|
|
if ((p = strstri(u_str, SP_detection)) != 0
|
|
&& !*(p + sizeof SP_detection - 1)) {
|
|
/* convert "<foo> detection" into "detect <foo>" */
|
|
*p = '\0';
|
|
Strcat(strcpy(buf, detect_SP), u_str);
|
|
/* "detect monster" -> "detect monsters" */
|
|
if (!strcmpi(u_str, "monster"))
|
|
Strcat(buf, "s");
|
|
*p = ' ';
|
|
return fuzzymatch(buf, o_str, " -", TRUE);
|
|
}
|
|
} else if (strstri(o_str, SP_detection)) {
|
|
/* and the inverse, "<foo> detection" vs "detect <foo>" */
|
|
if (!strncmpi(u_str, detect_SP, sizeof detect_SP - 1)) {
|
|
/* convert "detect <foo>s" into "<foo> detection" */
|
|
p = makesingular(u_str + sizeof detect_SP - 1);
|
|
Strcat(strcpy(buf, p), SP_detection);
|
|
/* caller may be looping through objects[], so avoid
|
|
churning through all the obufs */
|
|
releaseobuf(p);
|
|
return fuzzymatch(buf, o_str, " -", TRUE);
|
|
}
|
|
} else if (strstri(o_str, "ability")) {
|
|
/* when presented with "foo of bar", makesingular() used to
|
|
singularize both foo & bar, but now only does so for foo */
|
|
/* catch "{potion(s),ring} of {gain,restore,sustain} abilities" */
|
|
if ((p = strstri(u_str, "abilities")) != 0
|
|
&& !*(p + sizeof "abilities" - 1)) {
|
|
(void) strncpy(buf, u_str, (unsigned) (p - u_str));
|
|
Strcpy(buf + (p - u_str), "ability");
|
|
return fuzzymatch(buf, o_str, " -", TRUE);
|
|
}
|
|
} else if (!strcmp(o_str, "aluminum")) {
|
|
/* this special case doesn't really fit anywhere else... */
|
|
/* (note that " wand" will have been stripped off by now) */
|
|
if (!strcmpi(u_str, "aluminium"))
|
|
return fuzzymatch(u_str + 9, o_str + 8, " -", TRUE);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
struct o_range {
|
|
const char *name, oclass;
|
|
int f_o_range, l_o_range;
|
|
};
|
|
|
|
/* wishable subranges of objects */
|
|
static 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") */
|
|
static const 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 },
|
|
{ "amulet of protection", AMULET_OF_GUARDING },
|
|
{ "amulet of telepathy", AMULET_OF_ESP },
|
|
{ "helm of esp", HELM_OF_TELEPATHY },
|
|
{ "gauntlets of ogre power", GAUNTLETS_OF_POWER },
|
|
{ "gauntlets of giant strength", GAUNTLETS_OF_POWER },
|
|
{ "elven chain mail", ELVEN_MITHRIL_COAT },
|
|
{ "silver shield", SHIELD_OF_REFLECTION },
|
|
{ "potion of sleep", POT_SLEEPING },
|
|
{ "scroll of recharging", SCR_CHARGING },
|
|
{ "recharging", SCR_CHARGING },
|
|
{ "stone", ROCK },
|
|
{ "camera", EXPENSIVE_CAMERA },
|
|
{ "tee shirt", T_SHIRT },
|
|
{ "can", TIN },
|
|
{ "can opener", TIN_OPENER },
|
|
{ "kelp", KELP_FROND },
|
|
{ "eucalyptus", EUCALYPTUS_LEAF },
|
|
{ "lembas", LEMBAS_WAFER },
|
|
{ "tripe", TRIPE_RATION },
|
|
{ "cookie", FORTUNE_COOKIE },
|
|
{ "pie", CREAM_PIE },
|
|
{ "huge meatball", ENORMOUS_MEATBALL }, /* likely conflated name */
|
|
{ "huge chunk of meat", ENORMOUS_MEATBALL }, /* original name */
|
|
{ "marker", MAGIC_MARKER },
|
|
{ "hook", GRAPPLING_HOOK },
|
|
{ "grappling iron", GRAPPLING_HOOK },
|
|
{ "grapnel", GRAPPLING_HOOK },
|
|
{ "grapple", GRAPPLING_HOOK },
|
|
{ "protection from shape shifters", RIN_PROTECTION_FROM_SHAPE_CHAN },
|
|
{ "accuracy", RIN_INCREASE_ACCURACY },
|
|
/* if we ever add other sizes, move this to o_ranges[] with "bag" */
|
|
{ "box", LARGE_BOX },
|
|
/* normally we wouldn't have to worry about unnecessary <space>, but
|
|
" stone" will get stripped off, preventing a wishymatch; that actually
|
|
lets "flint stone" be a match, so we also accept bogus "flintstone" */
|
|
{ "luck stone", LUCKSTONE },
|
|
{ "load stone", LOADSTONE },
|
|
{ "touch stone", TOUCHSTONE },
|
|
{ "flintstone", FLINT },
|
|
{ (const char *) 0, 0 },
|
|
};
|
|
|
|
staticfn short
|
|
rnd_otyp_by_wpnskill(schar skill)
|
|
{
|
|
int i, n = 0;
|
|
short otyp = STRANGE_OBJECT;
|
|
|
|
for (i = svb.bases[WEAPON_CLASS];
|
|
i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++)
|
|
if (objects[i].oc_skill == skill) {
|
|
n++;
|
|
otyp = i;
|
|
}
|
|
if (n > 0) {
|
|
n = rn2(n);
|
|
for (i = svb.bases[WEAPON_CLASS];
|
|
i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++)
|
|
if (objects[i].oc_skill == skill)
|
|
if (--n < 0)
|
|
return i;
|
|
}
|
|
return otyp;
|
|
}
|
|
|
|
staticfn short
|
|
rnd_otyp_by_namedesc(
|
|
const char *name,
|
|
char oclass,
|
|
int xtra_prob) /* add to item's chance of being chosen; non-zero causes
|
|
* 0% random generation items to also be considered */
|
|
{
|
|
int i, n = 0;
|
|
short validobjs[NUM_OBJECTS];
|
|
const char *zn, *of;
|
|
boolean check_of;
|
|
int lo, hi, minglob, maxglob, prob, maxprob = 0;
|
|
|
|
if (!name || !*name)
|
|
return STRANGE_OBJECT;
|
|
|
|
/* only skip "foo of" for "foo of bar" if target doesn't contain " of " */
|
|
check_of = (strstri(name, " of ") == 0);
|
|
minglob = GLOB_OF_GRAY_OOZE;
|
|
maxglob = GLOB_OF_BLACK_PUDDING;
|
|
|
|
(void) memset((genericptr_t) validobjs, 0, sizeof validobjs);
|
|
if (oclass) {
|
|
lo = svb.bases[(uchar) oclass];
|
|
hi = svb.bases[(uchar) oclass + 1] - 1;
|
|
} else {
|
|
lo = MAXOCLASSES; /* STRANGE_OBJECT + 1; */
|
|
hi = NUM_OBJECTS - 1;
|
|
}
|
|
/* FIXME:
|
|
* When this spans classes (the !oclass case), the item
|
|
* probabilities are not very useful because they don't take
|
|
* the class generation probability into account. [If 10%
|
|
* of spellbooks were blank and 1% of scrolls were blank,
|
|
* "blank" would have 10/11 chance to yield a book even though
|
|
* scrolls are supposed to be much more common than books.]
|
|
*/
|
|
for (i = lo; i <= hi; ++i) {
|
|
/* don't match extra descriptions (w/o real name) */
|
|
if ((zn = OBJ_NAME(objects[i])) == 0)
|
|
continue;
|
|
if (wishymatch(name, zn, TRUE) /* objects[] name */
|
|
/* let "<bar>" match "<foo> of <bar>" (already does if foo is
|
|
an object class, but this is for lump of royal jelly,
|
|
clove of garlic, bag of tricks, &c) with a few exceptions:
|
|
for "opening", don't match "bell of opening"; for monster
|
|
type ooze/pudding/slime don't match glob of same since that
|
|
ought to match "corpse/egg/figurine of type" too but won't */
|
|
|| (check_of
|
|
&& i != BELL_OF_OPENING
|
|
&& (i < minglob || i > maxglob)
|
|
&& (of = strstri(zn, " of ")) != 0
|
|
&& wishymatch(name, of + 4, FALSE)) /* partial name */
|
|
|| ((zn = OBJ_DESCR(objects[i])) != 0
|
|
&& wishymatch(name, zn, FALSE)) /* objects[] description */
|
|
/* "cloth" should match "piece of cloth"; there's only one
|
|
description containing " of " so no special case handling */
|
|
|| (zn && check_of && (of = strstri(zn, " of ")) != 0
|
|
&& wishymatch(name, of + 4, FALSE)) /* partial description */
|
|
|| ((zn = objects[i].oc_uname) != 0
|
|
&& wishymatch(name, zn, FALSE)) /* user-called name */
|
|
) {
|
|
validobjs[n++] = (short) i;
|
|
maxprob += (objects[i].oc_prob + xtra_prob);
|
|
}
|
|
}
|
|
|
|
if (n > 0 && maxprob) {
|
|
prob = rn2(maxprob);
|
|
for (i = 0; i < n - 1; i++)
|
|
if ((prob -= (objects[validobjs[i]].oc_prob + xtra_prob)) < 0)
|
|
break;
|
|
return validobjs[i];
|
|
}
|
|
return STRANGE_OBJECT;
|
|
}
|
|
|
|
int
|
|
shiny_obj(char oclass)
|
|
{
|
|
return (int) rnd_otyp_by_namedesc("shiny", oclass, 0);
|
|
}
|
|
|
|
/* set wall under hero undiggable/unphaseable from string */
|
|
staticfn void
|
|
set_wallprop_from_str(char *bp)
|
|
{
|
|
int wall_prop = 0;
|
|
|
|
if (strstr(bp, "undiggable ") || strstr(bp, "nondiggable "))
|
|
wall_prop |= W_NONDIGGABLE;
|
|
if (strstr(bp, "unphaseable ") || strstr(bp, "nonpasswall "))
|
|
wall_prop |= W_NONPASSWALL;
|
|
/* |= because wall_info (aka flags) is overloaded with other stuff */
|
|
if (wall_prop)
|
|
levl[u.ux][u.uy].wall_info |= wall_prop;
|
|
}
|
|
|
|
/* in wizard mode, readobjnam() can accept wishes for traps and terrain */
|
|
staticfn struct obj *
|
|
wizterrainwish(struct _readobjnam_data *d)
|
|
{
|
|
struct rm *lev;
|
|
boolean madeterrain = FALSE, badterrain = FALSE, didblock, is_dbridge;
|
|
int trap;
|
|
unsigned oldtyp, ltyp;
|
|
coordxy x = u.ux, y = u.uy;
|
|
char *bp = d->bp, *p;
|
|
|
|
for (trap = NO_TRAP + 1; trap < TRAPNUM; trap++) {
|
|
struct trap *t;
|
|
const char *tname;
|
|
|
|
tname = trapname(trap, TRUE);
|
|
if (!str_start_is(bp, tname, TRUE))
|
|
continue;
|
|
/* found it; avoid stupid mistakes */
|
|
if (is_hole(trap) && !Can_fall_thru(&u.uz))
|
|
trap = ROCKTRAP;
|
|
if ((t = maketrap(x, y, trap)) != 0) {
|
|
trap = t->ttyp;
|
|
tname = trapname(trap, TRUE);
|
|
pline("%s%s.", An(tname),
|
|
(trap != MAGIC_PORTAL) ? "" : " to nowhere");
|
|
} else {
|
|
pline("Creation of %s failed.", an(tname));
|
|
}
|
|
return &hands_obj;
|
|
}
|
|
|
|
/* furniture and terrain (use at your own risk; can clobber stairs
|
|
or place furniture on existing traps which shouldn't be allowed) */
|
|
lev = &levl[x][y];
|
|
oldtyp = lev->typ;
|
|
is_dbridge = (oldtyp == DRAWBRIDGE_DOWN || oldtyp == DRAWBRIDGE_UP);
|
|
didblock = does_block(x, y, lev);
|
|
p = eos(bp);
|
|
if (!BSTRCMPI(bp, p - 8, "fountain")) {
|
|
lev->typ = FOUNTAIN;
|
|
if (oldtyp != FOUNTAIN)
|
|
svl.level.flags.nfountains++;
|
|
lev->looted = d->looted ? F_LOOTED : 0; /* overlays 'flags' */
|
|
lev->blessedftn = d->blessed || !strncmpi(bp, "magic ", 6);
|
|
pline("A %sfountain.", lev->blessedftn ? "magic " : "");
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 6, "throne")) {
|
|
lev->typ = THRONE;
|
|
lev->looted = d->looted ? T_LOOTED : 0; /* overlays 'flags' */
|
|
pline("A throne.");
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 4, "sink")) {
|
|
lev->typ = SINK;
|
|
if (oldtyp != SINK)
|
|
svl.level.flags.nsinks++;
|
|
lev->looted = d->looted ? (S_LPUDDING | S_LDWASHER | S_LRING) : 0;
|
|
pline("A sink.");
|
|
madeterrain = TRUE;
|
|
|
|
/* ("water" matches "potion of water" rather than terrain) */
|
|
} else if (!BSTRCMPI(bp, p - 4, "pool")
|
|
|| !BSTRCMPI(bp, p - 4, "moat")
|
|
|| !BSTRCMPI(bp, p - 13, "wall of water")) {
|
|
long save_prop;
|
|
const char *new_water;
|
|
|
|
ltyp = !BSTRCMPI(bp, p - 4, "pool") ? POOL
|
|
: !BSTRCMPI(bp, p - 4, "moat") ? MOAT
|
|
: WATER;
|
|
if (!is_dbridge) {
|
|
lev->typ = ltyp;
|
|
lev->flags = 0;
|
|
} else {
|
|
/* drawbridgemask overloads flags */
|
|
lev->drawbridgemask &= ~DB_UNDER;
|
|
lev->drawbridgemask |= DB_MOAT;
|
|
}
|
|
del_engr_at(x, y);
|
|
if (!is_dbridge) {
|
|
save_prop = EHalluc_resistance;
|
|
EHalluc_resistance = 1;
|
|
new_water = waterbody_name(x, y);
|
|
EHalluc_resistance = save_prop;
|
|
pline("%s.", An(new_water));
|
|
/* Must manually make kelp! */
|
|
} else {
|
|
dbterrainmesg("Moat", x, y);
|
|
}
|
|
water_damage_chain(svl.level.objects[x][y], TRUE);
|
|
madeterrain = TRUE;
|
|
|
|
/* also matches "molten lava" */
|
|
} else if (!BSTRCMPI(bp, p - 4, "lava")
|
|
|| !BSTRCMPI(bp, p - 12, "wall of lava")) {
|
|
ltyp = !BSTRCMPI(bp, p - 12, "wall of lava") ? LAVAWALL : LAVAPOOL;
|
|
if (!is_dbridge) {
|
|
lev->typ = ltyp;
|
|
lev->flags = 0;
|
|
} else {
|
|
/* drawbridgemask overloads flags */
|
|
lev->drawbridgemask &= ~DB_UNDER;
|
|
lev->drawbridgemask |= DB_LAVA;
|
|
}
|
|
del_engr_at(x, y);
|
|
if (!is_dbridge) {
|
|
pline("A %s of molten lava.",
|
|
(lev->typ == LAVAPOOL) ? "pool" : "wall");
|
|
if (!(Levitation || Flying) || lev->typ == LAVAWALL)
|
|
pooleffects(FALSE);
|
|
} else {
|
|
dbterrainmesg("Lava", x, y);
|
|
}
|
|
fire_damage_chain(svl.level.objects[x][y], TRUE, TRUE, x, y);
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 3, "ice")) {
|
|
if (!is_dbridge) {
|
|
lev->typ = ICE;
|
|
/* icedpool overloads flags; specifies what ice will melt into */
|
|
lev->icedpool = (oldtyp == ROOM) ? ICED_POOL : ICED_MOAT;
|
|
} else {
|
|
/* drawbridgemask overloads flags */
|
|
lev->drawbridgemask &= ~DB_UNDER;
|
|
lev->drawbridgemask |= DB_ICE;
|
|
}
|
|
del_engr_at(x, y);
|
|
|
|
if (!strncmpi(bp, "melting ", 8))
|
|
start_melt_ice_timeout(x, y, 0L);
|
|
|
|
if (!is_dbridge) {
|
|
char icebuf[40];
|
|
|
|
pline("%s.", upstart(ice_descr(x, y, icebuf)));
|
|
} else {
|
|
dbterrainmesg("Ice", x, y);
|
|
}
|
|
madeterrain = TRUE;
|
|
} else 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); /* overlays 'flags' */
|
|
pline("%s altar.", An(align_str(al)));
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 5, "grave")
|
|
|| !BSTRCMPI(bp, p - 9, "headstone")) {
|
|
make_grave(x, y, (char *) 0);
|
|
if (IS_GRAVE(lev->typ)) {
|
|
lev->looted = 0; /* overlays 'flags' */
|
|
lev->disturbed = d->looted ? 1 : 0;
|
|
pline("A %sgrave.", lev->disturbed ? "disturbed " : "");
|
|
madeterrain = TRUE;
|
|
} else {
|
|
pline("Can't place a grave here.");
|
|
badterrain = TRUE;
|
|
}
|
|
} else if (!BSTRCMPI(bp, p - 4, "tree")) {
|
|
lev->typ = TREE;
|
|
lev->looted = d->looted ? (TREE_LOOTED | TREE_SWARM) : 0;
|
|
set_wallprop_from_str(bp);
|
|
pline("A tree.");
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 4, "bars")) {
|
|
lev->typ = IRONBARS;
|
|
lev->flags = 0;
|
|
set_wallprop_from_str(bp);
|
|
/* [FIXME: if this isn't a wall or door location where 'horizontal'
|
|
is already set up, that should be calculated for this spot.
|
|
Unfortunately, it can be tricky; placing one in open space
|
|
and then another adjacent might need to recalculate first one.] */
|
|
pline("Iron bars.");
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 5, "cloud")) {
|
|
lev->typ = CLOUD;
|
|
lev->flags = 0;
|
|
pline("A cloud.");
|
|
del_engr_at(x, y);
|
|
madeterrain = TRUE;
|
|
} else if (!BSTRCMPI(bp, p - 4, "door")
|
|
|| (d->doorless && !BSTRCMPI(bp, p - 7, "doorway"))) {
|
|
char dbuf[40];
|
|
unsigned old_wall_info;
|
|
boolean secret = !BSTRCMPI(bp, p - 11, "secret door");
|
|
|
|
/* require door or wall so that the 'horizontal' flag will
|
|
already have the correct value; player might choose to put
|
|
DOOR on top of existing DOOR or SDOOR on top of existing SDOOR
|
|
to control its trapped state; iron bars are surrogate walls;
|
|
a previously dug wall looks like corridor but is actually a
|
|
doorless doorway so will be acceptable here */
|
|
if (lev->typ == DOOR || lev->typ == SDOOR
|
|
|| (IS_WALL(lev->typ) && lev->typ != DBWALL)
|
|
|| lev->typ == IRONBARS) {
|
|
/* remember previous wall info [is this right for iron bars?] */
|
|
old_wall_info = (lev->typ != DOOR) ? lev->wall_info : 0;
|
|
/* set the new terrain type */
|
|
lev->typ = secret ? SDOOR : DOOR;
|
|
lev->wall_info = 0; /* overlays 'flags' */
|
|
/* lev->horizontal stays as-is */
|
|
if (Is_rogue_level(&u.uz)) {
|
|
/* all doors on the rogue level are doorless; locking magic
|
|
there converts them into walls rather than closed doors */
|
|
d->doorless = 1;
|
|
d->locked = d->closed = d->open = d->broken = 0;
|
|
}
|
|
/* if not locked, secret doors are implicitly closed but
|
|
mustn't be set that way explicitly because they use both
|
|
doormask and wall_info which both overload rm[x][y].flags
|
|
(CLOSED overlaps wall_info bits, LOCKED and TRAPPED don't);
|
|
conversion from SDOOR to DOOR changes NODOOR to CLOSED */
|
|
lev->doormask = d->locked ? D_LOCKED
|
|
: (d->doorless || secret) ? D_NODOOR
|
|
: d->open ? D_ISOPEN
|
|
: d->broken ? D_BROKEN
|
|
: D_CLOSED;
|
|
/* SDOOR uses wall_info, restore relevant bits.
|
|
* FIXME? if we're changing a regular door into a secret door,
|
|
* old_wall_info bits will be 0 instead of being set properly.
|
|
* Probably only matters if player uses Passes_walls and a wish
|
|
* to turn a T- or cross-wall into a door, losing wall info,
|
|
* and then another wish to turn that door into a secret door. */
|
|
if (secret)
|
|
lev->wall_info |= (old_wall_info & WM_MASK);
|
|
/* set up trapped flag; open door states aren't eligible */
|
|
if (d->trapped == 2 /* 2: wish includes explicit "untrapped" */
|
|
|| secret /* secret doors can't be trapped due to their use
|
|
* of both doormask and wall_info; those both
|
|
* overlay rm->flags and partially conflict */
|
|
|| (lev->doormask & (D_LOCKED | D_CLOSED)) == 0)
|
|
d->trapped = 0;
|
|
if (d->trapped)
|
|
lev->doormask |= D_TRAPPED;
|
|
/* feedback */
|
|
dbuf[0] = '\0';
|
|
if (lev->doormask & D_TRAPPED)
|
|
Strcat(dbuf, "trapped ");
|
|
if (lev->doormask & D_LOCKED)
|
|
Strcat(dbuf, "locked ");
|
|
if (lev->typ == SDOOR) {
|
|
Strcat(dbuf, "secret door");
|
|
} else {
|
|
/* these should be mutually exclusive but we describe them
|
|
as if they're independent to maybe catch future bugs... */
|
|
if (lev->doormask & D_CLOSED)
|
|
Strcat(dbuf, "closed ");
|
|
if (lev->doormask & D_ISOPEN)
|
|
Strcat(dbuf, "open ");
|
|
if (lev->doormask & D_BROKEN)
|
|
Strcat(dbuf, "broken ");
|
|
if ((lev->doormask & ~D_TRAPPED) == D_NODOOR)
|
|
Strcat(dbuf, "doorless doorway");
|
|
else
|
|
Strcat(dbuf, "door");
|
|
}
|
|
pline("%s.", upstart(an(dbuf)));
|
|
madeterrain = TRUE;
|
|
} else {
|
|
Strcpy(dbuf, secret ? "secret door" : "door");
|
|
pline("%s requires door or wall location.", upstart(dbuf));
|
|
badterrain = TRUE;
|
|
}
|
|
} else if (!BSTRCMPI(bp, p - 4, "wall")
|
|
&& (bp == p - 4 || p[-5] == ' ')) {
|
|
schar wall = HWALL;
|
|
|
|
if ((isok(u.ux, u.uy-1) && IS_WALL(levl[u.ux][u.uy-1].typ))
|
|
|| (isok(u.ux, u.uy+1) && IS_WALL(levl[u.ux][u.uy+1].typ)))
|
|
wall = VWALL;
|
|
madeterrain = TRUE;
|
|
lev->typ = wall;
|
|
lev->flags = 0;
|
|
set_wallprop_from_str(bp);
|
|
fix_wall_spines(max(0,u.ux-1), max(0,u.uy-1),
|
|
min(COLNO,u.ux+1), min(ROWNO,u.uy+1));
|
|
pline("A wall.");
|
|
} else if (!BSTRCMPI(bp, p - 15, "secret corridor")) {
|
|
if (lev->typ == CORR) {
|
|
lev->typ = SCORR;
|
|
/* neither CORR nor SCORR uses 'flags' or 'horizontal' */
|
|
pline("Secret corridor.");
|
|
madeterrain = TRUE;
|
|
} else {
|
|
pline("Secret corridor requires corridor location.");
|
|
badterrain = TRUE;
|
|
}
|
|
} else if (!BSTRCMPI(bp, p - 4, "room")
|
|
|| !BSTRCMPI(bp, p - 5, "floor")
|
|
|| !BSTRCMPI(bp, p - 6, "ground")) {
|
|
if (oldtyp == ROOM
|
|
|| (IS_FURNITURE(oldtyp) && CAN_OVERWRITE_TERRAIN(oldtyp))
|
|
|| oldtyp == ICE || is_pool_or_lava(x, y)) {
|
|
struct trap *t;
|
|
|
|
lev->typ = ROOM;
|
|
pline("Room floor.");
|
|
if (IS_FURNITURE(oldtyp))
|
|
count_level_features();
|
|
if ((t = t_at(x, y)) != 0 && t->ttyp != MAGIC_PORTAL)
|
|
deltrap(t);
|
|
madeterrain = TRUE;
|
|
} else if (is_dbridge) {
|
|
lev->drawbridgemask &= ~DB_UNDER;
|
|
lev->drawbridgemask |= DB_FLOOR;
|
|
dbterrainmesg("Floor", x, y);
|
|
madeterrain = TRUE;
|
|
} else {
|
|
pline("Room|floor|ground not allowed here.");
|
|
badterrain = TRUE;
|
|
}
|
|
}
|
|
|
|
if (madeterrain) {
|
|
feel_newsym(x, y); /* map the spot where the wish occurred */
|
|
|
|
/* hero started at <x,y> but might not be there anymore (create
|
|
lava, decline to die, and get teleported away to safety) */
|
|
if (u.uinwater && !is_pool(u.ux, u.uy)) {
|
|
set_uinwater(0); /* u.uinwater = 0; leave the water */
|
|
docrt();
|
|
/* [block/unblock_point handled by docrt -> vision_recalc] */
|
|
} else {
|
|
if (u.utrap && u.utraptype == TT_LAVA && !is_lava(u.ux, u.uy))
|
|
reset_utrap(FALSE);
|
|
|
|
if (does_block(x, y, lev)) {
|
|
if (!didblock)
|
|
block_point(x, y);
|
|
} else {
|
|
if (didblock)
|
|
unblock_point(x, y);
|
|
}
|
|
}
|
|
|
|
/* fixups for replaced terrain that aren't handled above */
|
|
if (IS_FOUNTAIN(oldtyp) || IS_SINK(oldtyp))
|
|
count_level_features(); /* update level.flags.nfountains,nsinks */
|
|
if (!is_ice(x, y))
|
|
spot_stop_timers(x, y, MELT_ICE_AWAY);
|
|
/* horizontal is overlaid by fountain->blessedftn, grave->disturbed */
|
|
if (IS_FOUNTAIN(oldtyp) || IS_GRAVE(oldtyp)
|
|
|| IS_WALL(oldtyp) || oldtyp == IRONBARS
|
|
|| IS_DOOR(oldtyp) || oldtyp == SDOOR) {
|
|
/* when new terrain is a fountain, 'blessedftn' was explicitly
|
|
set above; likewise for grave and 'disturbed'; when it's a
|
|
door, the old type was a wall or a door and we retain the
|
|
'horizontal' value from those */
|
|
if (!IS_FOUNTAIN(lev->typ) && !IS_GRAVE(lev->typ)
|
|
&& !IS_DOOR(lev->typ) && lev->typ != SDOOR)
|
|
lev->horizontal = 0; /* also clears blessedftn, disturbed */
|
|
}
|
|
/* note: lev->lit and lev->nondiggable retain their values even
|
|
though those might not make sense with the new terrain */
|
|
|
|
/* might have changed terrain from something that blocked
|
|
levitation and flying to something that doesn't (levitating
|
|
while in xorn form and replacing solid stone with furniture) */
|
|
switch_terrain();
|
|
}
|
|
if (madeterrain || badterrain)
|
|
return &hands_obj;
|
|
|
|
return (struct obj *) 0;
|
|
}
|
|
|
|
/* message common to several wizterrainwish() results */
|
|
staticfn void
|
|
dbterrainmesg(
|
|
const char *newtype,
|
|
coordxy x, coordxy y)
|
|
{
|
|
pline("%s %s the drawbridge.", newtype,
|
|
(levl[x][y].typ == DRAWBRIDGE_UP) ? "in front of" : "under");
|
|
}
|
|
|
|
#define TIN_UNDEFINED 0
|
|
#define TIN_EMPTY 1
|
|
#define TIN_SPINACH 2
|
|
|
|
staticfn void
|
|
readobjnam_init(char *bp, struct _readobjnam_data *d)
|
|
{
|
|
d->otmp = (struct obj *) 0;
|
|
d->cnt = d->spe = d->spesgn = d->typ = 0;
|
|
d->very = d->rechrg = d->blessed = d->uncursed = d->iscursed
|
|
= d->ispoisoned = d->isgreased = d->eroded = d->eroded2
|
|
= d->erodeproof = d->halfeaten = d->islit = d->unlabeled
|
|
= d->ishistoric = d->isdiluted /* statues, potions */
|
|
/* box/chest and wizard mode door */
|
|
= d->trapped = d->locked = d->unlocked = d->broken
|
|
= d->open = d->closed = d->doorless /* wizard mode door */
|
|
= d->looted /* wizard mode fountain/sink/throne/tree and grave */
|
|
= d->real = d->fake = 0; /* Amulet */
|
|
d->tvariety = RANDOM_TIN;
|
|
d->mgend = -1; /* not specified, aka random */
|
|
d->mntmp = NON_PM;
|
|
d->contents = TIN_UNDEFINED;
|
|
d->oclass = 0;
|
|
d->actualn = d->dn = d->un = 0;
|
|
d->wetness = 0;
|
|
d->gsize = 0;
|
|
d->zombify = FALSE;
|
|
d->bp = d->origbp = bp;
|
|
d->p = (char *) 0;
|
|
d->name = (const char *) 0;
|
|
d->ftype = svc.context.current_fruit;
|
|
(void) memset(d->globbuf, '\0', sizeof d->globbuf);
|
|
(void) memset(d->fruitbuf, '\0', sizeof d->fruitbuf);
|
|
}
|
|
|
|
/* return 1 if d->bp is empty or contains only various qualifiers like
|
|
"blessed", "rustproof", and so on, or 0 if anything else is present */
|
|
staticfn int
|
|
readobjnam_preparse(struct _readobjnam_data *d)
|
|
{
|
|
char *save_bp = 0;
|
|
int more_l = 0, res = 1;
|
|
|
|
for (;;) {
|
|
int l;
|
|
|
|
if (!d->bp || !*d->bp)
|
|
break;
|
|
res = 0;
|
|
|
|
if (!strncmpi(d->bp, "an ", l = 3) || !strncmpi(d->bp, "a ", l = 2)) {
|
|
d->cnt = 1;
|
|
} else if (!strncmpi(d->bp, "the ", l = 4)) {
|
|
; /* just increment `bp' by `l' below */
|
|
} else if (!d->cnt && digit(*d->bp) && strcmp(d->bp, "0")) {
|
|
d->cnt = atoi(d->bp);
|
|
while (digit(*d->bp))
|
|
d->bp++;
|
|
while (*d->bp == ' ')
|
|
d->bp++;
|
|
l = 0;
|
|
} else if (*d->bp == '+' || *d->bp == '-') {
|
|
d->spesgn = (*d->bp++ == '+') ? 1 : -1;
|
|
d->spe = atoi(d->bp);
|
|
while (digit(*d->bp))
|
|
d->bp++;
|
|
while (*d->bp == ' ')
|
|
d->bp++;
|
|
l = 0;
|
|
} else if (!strncmpi(d->bp, "blessed ", l = 8)
|
|
|| !strncmpi(d->bp, "holy ", l = 5)) {
|
|
d->blessed = 1, d->uncursed = d->iscursed = 0;
|
|
} else if (!strncmpi(d->bp, "cursed ", l = 7)
|
|
|| !strncmpi(d->bp, "unholy ", l = 7)) {
|
|
d->iscursed = 1, d->blessed = d->uncursed = 0;
|
|
} else if (!strncmpi(d->bp, "uncursed ", l = 9)) {
|
|
d->uncursed = 1, d->blessed = d->iscursed = 0;
|
|
} else if (!strncmpi(d->bp, "rustproof ", l = 10)
|
|
|| !strncmpi(d->bp, "erodeproof ", l = 11)
|
|
|| !strncmpi(d->bp, "corrodeproof ", l = 13)
|
|
|| !strncmpi(d->bp, "fixed ", l = 6)
|
|
|| !strncmpi(d->bp, "fireproof ", l = 10)
|
|
|| !strncmpi(d->bp, "rotproof ", l = 9)
|
|
|| !strncmpi(d->bp, "tempered ", l = 9)
|
|
|| !strncmpi(d->bp, "crackproof ", l = 11)) {
|
|
d->erodeproof = 1;
|
|
} else if (!strncmpi(d->bp, "lit ", l = 4)
|
|
|| !strncmpi(d->bp, "burning ", l = 8)) {
|
|
d->islit = 1;
|
|
} else if (!strncmpi(d->bp, "unlit ", l = 6)
|
|
|| !strncmpi(d->bp, "extinguished ", l = 13)) {
|
|
d->islit = 0;
|
|
|
|
/* "wet" and "moist" are only applicable for towels */
|
|
} else if (!strncmpi(d->bp, "moist ", l = 6)
|
|
|| !strncmpi(d->bp, "wet ", l = 4)) {
|
|
if (!strncmpi(d->bp, "wet ", 4))
|
|
d->wetness = 3 + rn2(3); /* 3..5 */
|
|
else
|
|
d->wetness = rnd(2); /* 1..2 */
|
|
|
|
/* "unlabeled" and "blank" are synonymous */
|
|
} else if (!strncmpi(d->bp, "unlabeled ", l = 10)
|
|
|| !strncmpi(d->bp, "unlabelled ", l = 11)
|
|
|| !strncmpi(d->bp, "blank ", l = 6)) {
|
|
d->unlabeled = 1;
|
|
} else if (!strncmpi(d->bp, "poisoned ", l = 9)) {
|
|
d->ispoisoned = 1;
|
|
|
|
/* "trapped" recognized but not honored outside wizard mode */
|
|
} else if (!strncmpi(d->bp, "trapped ", l = 8)) {
|
|
d->trapped = 0; /* undo any previous "untrapped" */
|
|
if (wizard)
|
|
d->trapped = 1;
|
|
} else if (!strncmpi(d->bp, "untrapped ", l = 10)) {
|
|
d->trapped = 2; /* not trapped */
|
|
|
|
/* locked, unlocked, broken: box/chest lock states, also door states;
|
|
open, closed, doorless: additional door states */
|
|
} else if (!strncmpi(d->bp, "locked ", l = 7)) {
|
|
d->locked = d->closed = 1,
|
|
d->unlocked = d->broken = d->open = d->doorless = 0;
|
|
} else if (!strncmpi(d->bp, "unlocked ", l = 9)) {
|
|
d->unlocked = d->closed = 1,
|
|
d->locked = d->broken = d->open = d->doorless = 0;
|
|
} else if (!strncmpi(d->bp, "broken ", l = 7)) {
|
|
d->broken = 1,
|
|
d->locked = d->unlocked = d->open = d->closed
|
|
= d->doorless = 0;
|
|
} else if (!strncmpi(d->bp, "open ", l = 5)) {
|
|
d->open = 1,
|
|
d->closed = d->locked = d->broken = d->doorless = 0;
|
|
} else if (!strncmpi(d->bp, "closed ", l = 7)) {
|
|
d->closed = 1,
|
|
d->open = d->locked = d->broken = d->doorless = 0;
|
|
} else if (!strncmpi(d->bp, "doorless ", l = 9)) {
|
|
d->doorless = 1,
|
|
d->open = d->closed = d->locked = d->unlocked = d->broken = 0;
|
|
/* looted: fountain/sink/throne/tree; disturbed: grave */
|
|
} else if (!strncmpi(d->bp, "looted ", l = 7)
|
|
/* overload disturbed grave with looted fountain here
|
|
even though they're separate in struct rm */
|
|
|| !strncmpi(d->bp, "disturbed ", l = 10)) {
|
|
d->looted = 1;
|
|
} else if (!strncmpi(d->bp, "greased ", l = 8)) {
|
|
d->isgreased = 1;
|
|
} else if (!strncmpi(d->bp, "zombifying ", l = 11)) {
|
|
d->zombify = TRUE;
|
|
} else if (!strncmpi(d->bp, "very ", l = 5)) {
|
|
/* very rusted very heavy iron ball */
|
|
d->very = 1;
|
|
} else if (!strncmpi(d->bp, "thoroughly ", l = 11)) {
|
|
d->very = 2;
|
|
} else if (!strncmpi(d->bp, "rusty ", l = 6)
|
|
|| !strncmpi(d->bp, "rusted ", l = 7)
|
|
|| !strncmpi(d->bp, "burnt ", l = 6)
|
|
|| !strncmpi(d->bp, "burned ", l = 7)
|
|
|| !strncmpi(d->bp, "cracked ", l = 8)) {
|
|
d->eroded = 1 + d->very;
|
|
d->very = 0;
|
|
} else if (!strncmpi(d->bp, "corroded ", l = 9)
|
|
|| !strncmpi(d->bp, "rotted ", l = 7)) {
|
|
d->eroded2 = 1 + d->very;
|
|
d->very = 0;
|
|
} else if (!strncmpi(d->bp, "partly eaten ", l = 13)
|
|
|| !strncmpi(d->bp, "partially eaten ", l = 16)) {
|
|
d->halfeaten = 1;
|
|
} else if (!strncmpi(d->bp, "historic ", l = 9)) {
|
|
d->ishistoric = 1;
|
|
} else if (!strncmpi(d->bp, "diluted ", l = 8)) {
|
|
d->isdiluted = 1;
|
|
} else if (!strncmpi(d->bp, "empty ", l = 6)) {
|
|
d->contents = TIN_EMPTY;
|
|
} else if (!strncmpi(d->bp, "small ", l = 6)) { /* glob sizes */
|
|
/* "small" might be part of monster name (mimic, if wishing
|
|
for its corpse) rather than prefix for glob size; when
|
|
used for globs, it might be either "small glob of <foo>" or
|
|
"small <foo> glob" and user might add 's' even though plural
|
|
doesn't accomplish anything because globs don't stack */
|
|
if (strncmpi(d->bp + l, "glob", 4) && !strstri(d->bp + l, " glob"))
|
|
break;
|
|
d->gsize = 1;
|
|
} else if (!strncmpi(d->bp, "medium ", l = 7)) {
|
|
/* 3.7: in 3.6, "medium" was only used during wishing and the
|
|
mid-size glob had no adjective when formatted, but as of
|
|
3.7, "medium" has become an explicit part of the name for
|
|
combined globs of at least 5 individual ones (owt >= 100)
|
|
and less than 15 (owt < 300) */
|
|
d->gsize = 2;
|
|
} else if (!strncmpi(d->bp, "large ", l = 6)) {
|
|
/* "large" might be part of monster name (dog, cat, kobold,
|
|
mimic) or object name (box, round shield) rather than
|
|
prefix for glob size */
|
|
if (strncmpi(d->bp + l, "glob", 4) && !strstri(d->bp + l, " glob"))
|
|
break;
|
|
/* "very large " had "very " peeled off on previous iteration */
|
|
d->gsize = (d->very != 1) ? 3 : 4;
|
|
} else if (!strncmpi(d->bp, "real ", l = 5)) {
|
|
/* accept "real Amulet of Yendor" with "blessed" or "cursed"
|
|
or useless "erodeproof" before or after "real" ... */
|
|
d->real = 1; /* don't negate 'fake' here; "real fake amulet" and
|
|
* "fake real amulet" will both yield fake amulet
|
|
* (so will "real amulet" outside of wizard mode) */
|
|
} else if (!strncmpi(d->bp, "fake ", l = 5)) {
|
|
/* ... and "fake Amulet of Yendor" likewise */
|
|
d->fake = 1, d->real = 0;
|
|
/* ['real' isn't actually needed (unless we someday add
|
|
"real gem" for random non-glass, non-stone)] */
|
|
} else if (!strncmpi(d->bp, "female ", l = 7)) {
|
|
d->mgend = FEMALE;
|
|
/* if after "corpse/statue/figurine of", remove from string */
|
|
if (save_bp)
|
|
strsubst(d->bp, "female ", ""), l = 0;
|
|
} else if (!strncmpi(d->bp, "male ", l = 5)) {
|
|
d->mgend = MALE;
|
|
if (save_bp)
|
|
strsubst(d->bp, "male ", ""), l = 0;
|
|
} else if (!strncmpi(d->bp, "neuter ", l = 7)) {
|
|
d->mgend = NEUTRAL;
|
|
if (save_bp)
|
|
strsubst(d->bp, "neuter ", ""), l = 0;
|
|
|
|
/*
|
|
* Corpse/statue/figurine gender hack: in order to accept
|
|
* "statue of a female gnome ruler" for gnome queen we need
|
|
* to recognize and skip over "statue of [a ]". Otherwise
|
|
* we would only accept "female gnome ruler statue" and the
|
|
* viable but silly "female statue of a gnome ruler".
|
|
*/
|
|
} else if ((!strncmpi(d->bp, "corpse ", l = 7)
|
|
|| !strncmpi(d->bp, "statue ", l = 7)
|
|
|| !strncmpi(d->bp, "figurine ", l = 9))
|
|
&& !strncmpi(d->bp + l, "of ", more_l = 3)) {
|
|
save_bp = d->bp; /* we'll backtrack to here later */
|
|
l += more_l, more_l = 0;
|
|
if (!strncmpi(d->bp + l, "a ", more_l = 2)
|
|
|| !strncmpi(d->bp + l, "an ", more_l = 3)
|
|
|| !strncmpi(d->bp + l, "the ", more_l = 4))
|
|
l += more_l;
|
|
} else {
|
|
break;
|
|
}
|
|
d->bp += l;
|
|
}
|
|
if (save_bp)
|
|
d->bp = save_bp;
|
|
return res;
|
|
}
|
|
|
|
staticfn void
|
|
readobjnam_parse_charges(struct _readobjnam_data *d)
|
|
{
|
|
if (strlen(d->bp) > 1 && (d->p = strrchr(d->bp, '(')) != 0) {
|
|
boolean keeptrailingchars = TRUE;
|
|
int idx = 0;
|
|
|
|
if (d->p > d->bp && d->p[-1] == ' ')
|
|
idx = -1;
|
|
d->p[idx] = '\0'; /* terminate bp */
|
|
++d->p; /* advance past '(' */
|
|
if (!strncmpi(d->p, "lit)", 4)) {
|
|
d->islit = 1;
|
|
d->p += 4 - 1; /* point at ')' */
|
|
} else {
|
|
d->spe = atoi(d->p);
|
|
while (digit(*d->p))
|
|
d->p++;
|
|
if (*d->p == ':') {
|
|
d->p++;
|
|
d->rechrg = d->spe;
|
|
d->spe = atoi(d->p);
|
|
while (digit(*d->p))
|
|
d->p++;
|
|
}
|
|
if (*d->p != ')') {
|
|
d->spe = d->rechrg = 0;
|
|
/* mis-matched parentheses; rest of string will be ignored
|
|
* [probably we should restore everything back to '('
|
|
* instead since it might be part of "named ..."]
|
|
*/
|
|
keeptrailingchars = FALSE;
|
|
} else {
|
|
d->spesgn = 1;
|
|
}
|
|
}
|
|
if (keeptrailingchars) {
|
|
char *pp = eos(d->bp);
|
|
|
|
/* 'pp' points at 'pb's terminating '\0',
|
|
'p' points at ')' and will be incremented past it */
|
|
do {
|
|
*pp++ = *++d->p;
|
|
} while (*d->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 (d->spe < 0) {
|
|
d->spesgn = -1; /* cheaters get what they deserve */
|
|
d->spe = abs(d->spe);
|
|
}
|
|
/* cap on obj->spe is independent of (and less than) SCHAR_LIM */
|
|
if (d->spe > SPE_LIM)
|
|
d->spe = SPE_LIM; /* slime mold uses d.ftype, so not affected */
|
|
if (d->rechrg < 0 || d->rechrg > 7)
|
|
d->rechrg = 7; /* recharge_limit */
|
|
}
|
|
|
|
staticfn int
|
|
readobjnam_postparse1(struct _readobjnam_data *d)
|
|
{
|
|
int i;
|
|
|
|
/* 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 ((d->p = strstri(d->bp, " named ")) != 0) {
|
|
*d->p = 0;
|
|
/* note: if 'name' is too long, oname() will truncate it */
|
|
d->name = d->p + 7;
|
|
}
|
|
if ((d->p = strstri(d->bp, " called ")) != 0) {
|
|
*d->p = 0;
|
|
/* note: if 'un' is too long, obj lookup just won't match anything */
|
|
d->un = d->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(d->bp, o_ranges[i].name)) {
|
|
d->oclass = o_ranges[i].oclass;
|
|
return 1; /*goto srch;*/
|
|
}
|
|
}
|
|
if ((d->p = strstri(d->bp, " labeled ")) != 0) {
|
|
*d->p = 0;
|
|
d->dn = d->p + 9;
|
|
} else if ((d->p = strstri(d->bp, " labelled ")) != 0) {
|
|
*d->p = 0;
|
|
d->dn = d->p + 10;
|
|
}
|
|
if ((d->p = strstri(d->bp, " of spinach")) != 0) {
|
|
*d->p = 0;
|
|
d->contents = TIN_SPINACH;
|
|
}
|
|
/* real vs fake is only useful for wizard mode but we'll accept its
|
|
parsing in normal play (result is never real Amulet for that case) */
|
|
if ((d->p = strstri(d->bp, OBJ_DESCR(objects[AMULET_OF_YENDOR]))) != 0
|
|
&& (d->p == d->bp || d->p[-1] == ' ')) {
|
|
char *s = d->bp;
|
|
|
|
/* "Amulet of Yendor" matches two items, name of real Amulet
|
|
and description of fake one; player can explicitly specify
|
|
"real" to disambiguate, but not specifying "fake" achieves
|
|
the same thing; "real" and "fake" are parsed above with other
|
|
prefixes so that combinations like "blessed real" and "real
|
|
blessed" work as expected; also accept partial specification
|
|
of the full name of the fake; unlike the prefix recognition
|
|
loop above, these have to be in the right order when more
|
|
than one is present (similar to worthless glass gems below) */
|
|
if (!strncmpi(s, "cheap ", 6))
|
|
d->fake = 1, s += 6;
|
|
if (!strncmpi(s, "plastic ", 8))
|
|
d->fake = 1, s += 8;
|
|
if (!strncmpi(s, "imitation ", 10))
|
|
d->fake = 1, s += 10;
|
|
nhUse(s); /* suppress potential assigned-but-not-used complaint */
|
|
/* when 'fake' is True, it overrides 'real' if both were given;
|
|
when it is False, force 'real' whether that was specified or not */
|
|
d->real = !d->fake;
|
|
d->typ = d->real ? AMULET_OF_YENDOR : FAKE_AMULET_OF_YENDOR;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
|
|
/*
|
|
* Skip over "pair of ", "pairs of", "set of" and "sets of".
|
|
*
|
|
* Accept "3 pair of boots" as well as "3 pairs of boots". It is
|
|
* valid English either way. See makeplural() for more on pair/pairs.
|
|
*
|
|
* We should only double count if the object in question is not
|
|
* referred to as a "pair of". E.g. We should double if the player
|
|
* types "pair of spears", but not if the player types "pair of
|
|
* lenses". Luckily (?) all objects that are referred to as pairs
|
|
* -- boots, gloves, and lenses -- are also not mergeable, so cnt is
|
|
* ignored anyway.
|
|
*/
|
|
if (!strncmpi(d->bp, "pair of ", 8)) {
|
|
d->bp += 8;
|
|
d->cnt *= 2;
|
|
} else if (!strncmpi(d->bp, "pairs of ", 9)) {
|
|
d->bp += 9;
|
|
if (d->cnt > 1)
|
|
d->cnt *= 2;
|
|
} else if (!strncmpi(d->bp, "set of ", 7)) {
|
|
d->bp += 7;
|
|
} else if (!strncmpi(d->bp, "sets of ", 8)) {
|
|
d->bp += 8;
|
|
}
|
|
|
|
/* Intercept pudding globs here; they're a valid wish target,
|
|
* but we need them to not get treated like a corpse.
|
|
* If a count is specified, it will be used to magnify weight
|
|
* rather than to specify quantity (which is always 1 for globs).
|
|
*/
|
|
i = (int) strlen(d->bp);
|
|
d->p = (char *) 0;
|
|
/* check for "glob", "<foo> glob", and "glob of <foo>" */
|
|
if (!strcmpi(d->bp, "glob") || !BSTRCMPI(d->bp, d->bp + i - 5, " glob")
|
|
|| !strcmpi(d->bp, "globs")
|
|
|| !BSTRCMPI(d->bp, d->bp + i - 6, " globs")
|
|
|| (d->p = strstri(d->bp, "glob of ")) != 0
|
|
|| (d->p = strstri(d->bp, "globs of ")) != 0) {
|
|
d->mntmp = name_to_mon(!d->p ? d->bp
|
|
: (strstri(d->p, " of ") + 4), (int *) 0);
|
|
/* if we didn't recognize monster type, pick a valid one at random */
|
|
if (d->mntmp == NON_PM)
|
|
d->mntmp = rn1(PM_BLACK_PUDDING - PM_GRAY_OOZE, PM_GRAY_OOZE);
|
|
/* normally this would be done when makesingular() changes the value
|
|
but canonical form here is already singular so that won't happen */
|
|
if (d->cnt < 2 && strstri(d->bp, "globs"))
|
|
d->cnt = 2; /* affects otmp->owt but not otmp->quan for globs */
|
|
/* construct canonical spelling in case name_to_mon() recognized a
|
|
variant (grey ooze) or player used inverted syntax (<foo> glob);
|
|
if player has given a valid monster type but not valid glob type,
|
|
object name lookup won't find it and wish attempt will fail */
|
|
Sprintf(d->globbuf, "glob of %s", mons[d->mntmp].pmnames[NEUTRAL]);
|
|
d->bp = d->globbuf;
|
|
d->mntmp = NON_PM; /* not useful for "glob of <foo>" object lookup */
|
|
d->oclass = FOOD_CLASS;
|
|
d->actualn = d->bp, d->dn = 0;
|
|
return 1; /*goto srch;*/
|
|
} else {
|
|
/*
|
|
* Find corpse type using "of" (figurine of an orc, tin of orc meat)
|
|
* Don't check if it's a wand or spellbook.
|
|
* (avoid "wand/finger of death" confusion).
|
|
* Don't match "ogre" or "giant" monster name inside alternate item
|
|
* names "gauntlets of ogre power" and "gauntlets of giant strength"
|
|
* (or the alternate spelling of those, "gloves of ...").
|
|
*/
|
|
if (!strstri(d->bp, "wand ") && !strstri(d->bp, "spellbook ")
|
|
&& !strstri(d->bp, "gauntlets ") && !strstri(d->bp, "gloves ")
|
|
&& !strstri(d->bp, "finger ")) {
|
|
if ((d->p = strstri(d->bp, "tin of ")) != 0) {
|
|
if (!strcmpi(d->p + 7, "spinach")) {
|
|
d->contents = TIN_SPINACH;
|
|
d->mntmp = NON_PM;
|
|
} else {
|
|
d->tmp = tin_variety_txt(d->p + 7, &d->tinv);
|
|
d->tvariety = d->tinv;
|
|
d->mntmp = name_to_mon(d->p + 7 + d->tmp, &d->mgend);
|
|
}
|
|
d->typ = TIN;
|
|
return 2; /*goto typfnd;*/
|
|
} else if ((d->p = strstri(d->bp, " of ")) != 0
|
|
&& ((d->mntmp = name_to_mon(d->p + 4, &d->mgend))
|
|
>= LOW_PM))
|
|
*d->p = 0;
|
|
}
|
|
}
|
|
/* Find corpse type w/o "of" (red dragon scale mail, yeti corpse) */
|
|
if (strncmpi(d->bp, "samurai sword", 13) /* not the "samurai" monster! */
|
|
&& strncmpi(d->bp, "wizard lock", 11) /* not the "wizard" monster! */
|
|
&& strncmpi(d->bp, "death wand", 10) /* 'of inversion', not Rider */
|
|
&& strncmpi(d->bp, "master key", 10) /* not the "master" rank */
|
|
&& strncmpi(d->bp, "ninja-to", 8) /* not the "ninja" rank */
|
|
&& strncmpi(d->bp, "magenta", 7)) { /* not the "mage" rank */
|
|
const char *rest = 0;
|
|
|
|
if (d->mntmp < LOW_PM && strlen(d->bp) > 2
|
|
&& ((d->mntmp = name_to_monplus(d->bp, &rest, &d->mgend))
|
|
>= LOW_PM)) {
|
|
char *obp = d->bp;
|
|
|
|
/* 'rest' is a pointer past the matching portion; if that was
|
|
an alternate name or a rank title rather than the canonical
|
|
monster name we wouldn't otherwise know how much to skip */
|
|
d->bp = (char *) rest; /* cast away const */
|
|
|
|
if (*d->bp == ' ') {
|
|
d->bp++;
|
|
} else if (!strncmpi(d->bp, "s ", 2)
|
|
|| (d->bp > d->origbp
|
|
&& !strncmpi(d->bp - 1, "s' ", 3))) {
|
|
d->bp += 2;
|
|
} else if (!strncmpi(d->bp, "es ", 3)
|
|
|| !strncmpi(d->bp, "'s ", 3)) {
|
|
d->bp += 3;
|
|
} else if (!*d->bp && !d->actualn && !d->dn && !d->un
|
|
&& !d->oclass) {
|
|
/* no referent; they don't really mean a monster type */
|
|
d->bp = obp;
|
|
d->mntmp = NON_PM;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* first change to singular if necessary */
|
|
if (*d->bp
|
|
/* we want "tricks" to match "bag of tricks" [rnd_otyp_by_namedesc()]
|
|
but that wouldn't work if it gets singularized to "trick"
|
|
["tricks bag" matches whether or not this exception is present
|
|
because singularize operates on "bag" and wishymatch()'s
|
|
'of inversion' finds a match] */
|
|
&& strcmpi(d->bp, "tricks")
|
|
/* an odd potential wish; fail rather than get a false match with
|
|
"cloth" because it might yield a "cloth spellbook" rather than
|
|
a "piece of cloth" cloak [maybe we should give random armor?] */
|
|
&& strcmpi(d->bp, "clothes")
|
|
) {
|
|
char *sng = makesingular(d->bp);
|
|
|
|
if (strcmp(d->bp, sng)) {
|
|
if (d->cnt == 1)
|
|
d->cnt = 2;
|
|
Strcpy(d->bp, sng);
|
|
}
|
|
}
|
|
|
|
/* Alternate spellings (pick-ax, silver sabre, &c) */
|
|
{
|
|
const struct alt_spellings *as = spellings;
|
|
|
|
while (as->sp) {
|
|
if (wishymatch(d->bp, as->sp, TRUE)) {
|
|
d->typ = as->ob;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
as++;
|
|
}
|
|
/* can't use spellings list for this one due to shuffling */
|
|
if (!strncmpi(d->bp, "grey spell", 10))
|
|
*(d->bp + 2) = 'a';
|
|
|
|
if ((d->p = strstri(d->bp, "armour")) != 0) {
|
|
/* skip past "armo", then copy remainder beyond "u" */
|
|
d->p += 4;
|
|
while ((*d->p = *(d->p + 1)) != '\0')
|
|
++d->p; /* self terminating */
|
|
}
|
|
}
|
|
|
|
/* dragon scales - assumes order of dragons */
|
|
if (!strcmpi(d->bp, "scales") && d->mntmp >= PM_GRAY_DRAGON
|
|
&& d->mntmp <= PM_YELLOW_DRAGON) {
|
|
d->typ = GRAY_DRAGON_SCALES + d->mntmp - PM_GRAY_DRAGON;
|
|
d->mntmp = NON_PM; /* no monster */
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
|
|
d->p = eos(d->bp);
|
|
if (!BSTRCMPI(d->bp, d->p - 10, "holy water")) {
|
|
/* this isn't needed for "[un]holy water" because adjective parsing
|
|
handles holy==blessed and unholy==cursed and leaves "water" for
|
|
the object type, but it is needed for "potion of [un]holy water"
|
|
since that parsing stops when it reaches "potion"; also, neither
|
|
"holy water" nor "unholy water" is an actual type of potion */
|
|
if (!BSTRNCMPI(d->bp, d->p - 10 - 2, "un", 2))
|
|
d->iscursed = 1, d->blessed = d->uncursed = 0; /* unholy water */
|
|
else
|
|
d->blessed = 1, d->iscursed = d->uncursed = 0; /* holy water */
|
|
d->typ = POT_WATER;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
/* accept "paperback" or "paperback book", reject "paperback spellbook" */
|
|
if (!strncmpi(d->bp, "paperback", 9)) {
|
|
char *dbp = d->bp + 9; /* just past "paperback" */
|
|
|
|
if (!*dbp || !strncmpi(dbp, " book", 5)) {
|
|
d->typ = SPE_NOVEL;
|
|
return 2; /*goto typfnd;*/
|
|
} else {
|
|
d->otmp = (struct obj *) 0;
|
|
return 3;
|
|
}
|
|
}
|
|
if (d->unlabeled && !BSTRCMPI(d->bp, d->p - 6, "scroll")) {
|
|
d->typ = SCR_BLANK_PAPER;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
if (d->unlabeled && !BSTRCMPI(d->bp, d->p - 9, "spellbook")) {
|
|
d->typ = SPE_BLANK_PAPER;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
/* specific food rather than color of gem/potion/spellbook[/scales] */
|
|
if (!BSTRCMPI(d->bp, d->p - 6, "orange") && d->mntmp == NON_PM) {
|
|
d->typ = ORANGE;
|
|
return 2; /*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(d->bp, d->p - 10, "gold piece")
|
|
|| !BSTRCMPI(d->bp, d->p - 7, "zorkmid")
|
|
|| !strcmpi(d->bp, "gold") || !strcmpi(d->bp, "money")
|
|
|| !strcmpi(d->bp, "coin") || *d->bp == GOLD_SYM) {
|
|
if (d->cnt > 5000 && !wizard)
|
|
d->cnt = 5000;
|
|
else if (d->cnt < 1)
|
|
d->cnt = 1;
|
|
d->otmp = mksobj(GOLD_PIECE, FALSE, FALSE);
|
|
d->otmp->quan = (long) d->cnt;
|
|
d->otmp->owt = weight(d->otmp);
|
|
disp.botl = TRUE;
|
|
return 3; /*return otmp;*/
|
|
}
|
|
|
|
/* check for single character object class code ("/" for wand, &c) */
|
|
if (strlen(d->bp) == 1 && (i = def_char_to_objclass(*d->bp)) < MAXOCLASSES
|
|
&& i > ILLOBJ_CLASS && (i != VENOM_CLASS || wizard)) {
|
|
d->oclass = i;
|
|
return 4; /*goto any;*/
|
|
}
|
|
|
|
/* Search for class names: XXXXX potion, scroll of XXXXX. Avoid */
|
|
/* false hits on, e.g., rings for "ring mail". */
|
|
if (strncmpi(d->bp, "enchant ", 8)
|
|
&& strncmpi(d->bp, "destroy ", 8)
|
|
&& strncmpi(d->bp, "detect food", 11)
|
|
&& strncmpi(d->bp, "food detection", 14)
|
|
&& strncmpi(d->bp, "ring mail", 9)
|
|
&& strncmpi(d->bp, "studded leather armor", 21)
|
|
&& strncmpi(d->bp, "leather armor", 13)
|
|
&& strncmpi(d->bp, "tooled horn", 11)
|
|
&& strncmpi(d->bp, "food ration", 11)
|
|
&& strncmpi(d->bp, "meat ring", 9))
|
|
for (i = 0; i < (int) (sizeof wrpsym); i++) {
|
|
int j = Strlen(wrp[i]);
|
|
|
|
/* check for "<class> [ of ] something" */
|
|
if (!strncmpi(d->bp, wrp[i], j)) {
|
|
d->oclass = wrpsym[i];
|
|
if (d->oclass != AMULET_CLASS) {
|
|
d->bp += j;
|
|
if (!strncmpi(d->bp, " of ", 4))
|
|
d->actualn = d->bp + 4;
|
|
/* else if(*bp) ?? */
|
|
} else
|
|
d->actualn = d->bp;
|
|
return 1; /*goto srch;*/
|
|
}
|
|
/* check for "something <class>" */
|
|
if (!BSTRCMPI(d->bp, d->p - j, wrp[i])) {
|
|
d->oclass = wrpsym[i];
|
|
/* for "foo amulet", leave the class name so that
|
|
wishymatch() can do "of inversion" to try matching
|
|
"amulet of foo"; other classes don't include their
|
|
class name in their full object names (where
|
|
"potion of healing" is just "healing", for instance) */
|
|
if (d->oclass != AMULET_CLASS) {
|
|
d->p -= j;
|
|
*d->p = '\0';
|
|
if (d->p > d->bp && d->p[-1] == ' ')
|
|
d->p[-1] = '\0';
|
|
} else {
|
|
/* amulet without "of"; convoluted wording but better a
|
|
special case that's handled than one that's missing */
|
|
if (!strncmpi(d->bp, "versus poison ", 14)) {
|
|
d->typ = AMULET_VERSUS_POISON;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
}
|
|
d->actualn = d->dn = d->bp;
|
|
return 1; /*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(d->bp, "bear", 4)
|
|
|| !strncmpi(d->bp, "land", 4))) {
|
|
boolean beartrap = (lowc(*d->bp) == 'b');
|
|
char *zp = d->bp + 4; /* skip "bear"/"land" */
|
|
|
|
if (*zp == ' ')
|
|
++zp; /* embedded space is optional */
|
|
if (!strncmpi(zp, beartrap ? "trap" : "mine", 4)) {
|
|
zp += 4;
|
|
if (d->trapped == 2 || !strcmpi(zp, " object")) {
|
|
/* "untrapped <foo>" or "<foo> object" */
|
|
d->typ = beartrap ? BEARTRAP : LAND_MINE;
|
|
return 2; /*goto typfnd;*/
|
|
} else if (d->trapped == 1 || *zp != '\0') {
|
|
/* "trapped <foo>" or "<foo> trap" (actually "<foo>*") */
|
|
/* use canonical trap spelling, skip object matching */
|
|
Strcpy(d->bp, trapname(beartrap ? BEAR_TRAP : LANDMINE, TRUE));
|
|
return 5; /*goto wiztrap;*/
|
|
}
|
|
/* [no prefix or suffix; we're going to end up matching
|
|
the object name and getting a disarmed trap object] */
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
staticfn int
|
|
readobjnam_postparse2(struct _readobjnam_data *d)
|
|
{
|
|
int i;
|
|
|
|
/* "grey stone" check must be before general "stone" */
|
|
for (i = 0; i < SIZE(o_ranges); i++)
|
|
if (!strcmpi(d->bp, o_ranges[i].name)) {
|
|
d->typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range);
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
|
|
if (!BSTRCMPI(d->bp, d->p - 6, " stone")
|
|
|| !BSTRCMPI(d->bp, d->p - 4, " gem")) {
|
|
d->p[!strcmpi(d->p - 4, " gem") ? -4 : -6] = '\0';
|
|
d->oclass = GEM_CLASS;
|
|
d->dn = d->actualn = d->bp;
|
|
return 1; /*goto srch;*/
|
|
} else if (!strcmpi(d->bp, "looking glass")) {
|
|
; /* avoid false hit on "* glass" */
|
|
} else if (!BSTRCMPI(d->bp, d->p - 6, " glass")
|
|
|| !strcmpi(d->bp, "glass")) {
|
|
char *s = d->bp;
|
|
|
|
/* treat "broken glass" as a non-existent item; since "broken" is
|
|
also a chest/box prefix it might have been stripped off above */
|
|
if (d->broken || strstri(s, "broken")) {
|
|
d->otmp = (struct obj *) 0;
|
|
return 3; /* return otmp */
|
|
}
|
|
if (!strncmpi(s, "worthless ", 10))
|
|
s += 10;
|
|
if (!strncmpi(s, "piece of ", 9))
|
|
s += 9;
|
|
if (!strncmpi(s, "colored ", 8))
|
|
s += 8;
|
|
else if (!strncmpi(s, "coloured ", 9))
|
|
s += 9;
|
|
if (!strcmpi(s, "glass")) { /* choose random color */
|
|
/* 9 different kinds */
|
|
d->typ = FIRST_GLASS_GEM + rn2(NUM_GLASS_GEMS);
|
|
if (objects[d->typ].oc_class == GEM_CLASS)
|
|
return 2; /*goto typfnd;*/
|
|
else
|
|
d->typ = 0; /* somebody changed objects[]? punt */
|
|
} else { /* try to construct canonical form */
|
|
char tbuf[BUFSZ];
|
|
|
|
Strcpy(tbuf, "worthless piece of ");
|
|
Strcat(tbuf, s); /* assume it starts with the color */
|
|
Strcpy(d->bp, tbuf);
|
|
}
|
|
}
|
|
|
|
d->actualn = d->bp;
|
|
if (!d->dn)
|
|
d->dn = d->actualn; /* ex. "skull cap" */
|
|
|
|
return 0;
|
|
}
|
|
|
|
staticfn int
|
|
readobjnam_postparse3(struct _readobjnam_data *d)
|
|
{
|
|
int i;
|
|
|
|
/* check real names of gems first */
|
|
if (!d->oclass && d->actualn) {
|
|
for (i = svb.bases[GEM_CLASS]; i <= LAST_REAL_GEM; i++) {
|
|
const char *zn;
|
|
|
|
if ((zn = OBJ_NAME(objects[i])) != 0 && !strcmpi(d->actualn, zn)) {
|
|
d->typ = i;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
}
|
|
/* "tin of foo" would be caught above, but plain "tin" has
|
|
a random chance of yielding "tin wand" unless we do this */
|
|
if (!strcmpi(d->actualn, "tin")) {
|
|
d->typ = TIN;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
}
|
|
|
|
if (((d->typ = rnd_otyp_by_namedesc(d->actualn, d->oclass, 1))
|
|
!= STRANGE_OBJECT)
|
|
|| (d->dn != d->actualn
|
|
&& ((d->typ = rnd_otyp_by_namedesc(d->dn, d->oclass, 1))
|
|
!= STRANGE_OBJECT))
|
|
|| ((d->typ = rnd_otyp_by_namedesc(d->un, d->oclass, 1))
|
|
!= STRANGE_OBJECT)
|
|
|| (d->origbp != d->actualn
|
|
&& ((d->typ = rnd_otyp_by_namedesc(d->origbp, d->oclass, 1))
|
|
!= STRANGE_OBJECT)))
|
|
return 2; /*goto typfnd;*/
|
|
d->typ = 0;
|
|
|
|
if (d->actualn) {
|
|
const struct Jitem *j = Japanese_items;
|
|
|
|
while (j->item) {
|
|
if (!strcmpi(d->actualn, j->name)) {
|
|
d->typ = j->item;
|
|
return 2; /*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 (d->oclass == ARMOR_CLASS && !strstri(d->bp, "mail")) {
|
|
/* modifying bp's string is ok; we're about to resort
|
|
to random armor if this also fails to match anything */
|
|
Strcat(d->bp, " mail");
|
|
return 6; /*goto retry;*/
|
|
}
|
|
if (!strcmpi(d->bp, "spinach")) {
|
|
d->contents = TIN_SPINACH;
|
|
d->typ = TIN;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
/* Fruits must 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.
|
|
*/
|
|
/* 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;
|
|
struct fruit *f;
|
|
|
|
blessedf = iscursedf = uncursedf = halfeatenf = 0;
|
|
cntf = 0;
|
|
|
|
fp = d->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 = gf.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) {
|
|
d->typ = SLIME_MOLD;
|
|
d->blessed = blessedf;
|
|
d->iscursed = iscursedf;
|
|
d->uncursed = uncursedf;
|
|
d->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;
|
|
d->cnt = cntf;
|
|
d->ftype = f->fid;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!d->oclass && d->actualn) {
|
|
short objtyp;
|
|
|
|
/* Perhaps it's an artifact specified by name, not type */
|
|
d->name = artifact_name(d->actualn, &objtyp, TRUE);
|
|
if (d->name) {
|
|
d->typ = objtyp;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
}
|
|
|
|
/* got a class, but not specific type;
|
|
check alternate spellings of items with matching classes */
|
|
if (d->oclass && !d->typ) {
|
|
const struct alt_spellings *as = spellings;
|
|
|
|
while (as->sp) {
|
|
if (objects[as->ob].oc_class == d->oclass
|
|
&& wishymatch(d->bp, as->sp, TRUE)) {
|
|
d->typ = as->ob;
|
|
return 2; /*goto typfnd;*/
|
|
}
|
|
as++;
|
|
}
|
|
}
|
|
|
|
return 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 &hands_obj; if an error (no matching object),
|
|
* return null.
|
|
*/
|
|
struct obj *
|
|
readobjnam(char *bp, struct obj *no_wish)
|
|
{
|
|
struct _readobjnam_data d;
|
|
|
|
readobjnam_init(bp, &d);
|
|
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(d.fruitbuf, bp);
|
|
|
|
if (readobjnam_preparse(&d))
|
|
goto any;
|
|
|
|
if (!d.cnt)
|
|
d.cnt = 1; /* will be changed to 2 if makesingular() changes string */
|
|
|
|
readobjnam_parse_charges(&d);
|
|
|
|
switch (readobjnam_postparse1(&d)) {
|
|
default:
|
|
case 0: break;
|
|
case 1: goto srch;
|
|
case 2: goto typfnd;
|
|
case 3: return d.otmp;
|
|
case 4: goto any;
|
|
case 5: goto wiztrap;
|
|
}
|
|
|
|
retry:
|
|
switch (readobjnam_postparse2(&d)) {
|
|
default:
|
|
case 0: break;
|
|
case 1: goto srch;
|
|
case 2: goto typfnd;
|
|
case 3: return d.otmp;
|
|
case 4: goto any;
|
|
case 5: goto wiztrap;
|
|
}
|
|
|
|
srch:
|
|
switch (readobjnam_postparse3(&d)) {
|
|
default:
|
|
case 0: break;
|
|
case 1: goto srch;
|
|
case 2: goto typfnd;
|
|
case 3: return d.otmp;
|
|
case 4: goto any;
|
|
case 5: goto wiztrap;
|
|
case 6: goto retry;
|
|
}
|
|
|
|
/*
|
|
* 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 && !d.oclass) {
|
|
/* [inline code moved to separate routine to unclutter readobjnam] */
|
|
if ((d.otmp = wizterrainwish(&d)) != 0)
|
|
return d.otmp;
|
|
}
|
|
|
|
if (!d.oclass && !d.typ) {
|
|
if (!strncmpi(d.bp, "polearm", 7)) {
|
|
d.typ = rnd_otyp_by_wpnskill(P_POLEARMS);
|
|
goto typfnd;
|
|
} else if (!strncmpi(d.bp, "hammer", 6)) {
|
|
d.typ = rnd_otyp_by_wpnskill(P_HAMMER);
|
|
goto typfnd;
|
|
}
|
|
}
|
|
|
|
if (!d.oclass)
|
|
return ((struct obj *) 0);
|
|
any:
|
|
if (!d.oclass)
|
|
d.oclass = wrpsym[rn2((int) sizeof wrpsym)];
|
|
typfnd:
|
|
if (d.typ)
|
|
d.oclass = objects[d.typ].oc_class;
|
|
|
|
/* handle some objects that are only allowed in wizard mode */
|
|
if (d.typ && !wizard) {
|
|
switch (d.typ) {
|
|
case AMULET_OF_YENDOR:
|
|
d.typ = FAKE_AMULET_OF_YENDOR;
|
|
break;
|
|
case CANDELABRUM_OF_INVOCATION:
|
|
d.typ = rnd_class(TALLOW_CANDLE, WAX_CANDLE);
|
|
break;
|
|
case BELL_OF_OPENING:
|
|
d.typ = BELL;
|
|
break;
|
|
case SPE_BOOK_OF_THE_DEAD:
|
|
d.typ = SPE_BLANK_PAPER;
|
|
break;
|
|
case MAGIC_LAMP:
|
|
d.typ = OIL_LAMP;
|
|
break;
|
|
default:
|
|
/* catch any other non-wishable objects (venom) */
|
|
if (objects[d.typ].oc_nowish)
|
|
return (struct obj *) 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if asking for corpse of a monster which leaves behind a glob, give
|
|
glob instead of rejecting the monster type to create random corpse */
|
|
if (d.typ == CORPSE && d.mntmp >= LOW_PM
|
|
&& mons[d.mntmp].mlet == S_PUDDING) {
|
|
d.typ = GLOB_OF_GRAY_OOZE + (d.mntmp - PM_GRAY_OOZE);
|
|
d.mntmp = NON_PM; /* not used for globs */
|
|
}
|
|
/*
|
|
* Create the object, then fine-tune it.
|
|
*/
|
|
d.otmp = d.typ ? mksobj(d.typ, TRUE, FALSE) : mkobj(d.oclass, FALSE);
|
|
d.typ = d.otmp->otyp, d.oclass = d.otmp->oclass; /* what we actually got */
|
|
|
|
/* if player specified a reasonable count, maybe honor it;
|
|
quantity for gold is handled elsewhere and d.cnt is 0 for it here */
|
|
if (d.otmp->globby) {
|
|
/* for globs, calculate weight based on gsize, then multiply by cnt;
|
|
asking for 2 globs or for 2 small globs produces 1 small glob
|
|
weighing 40au instead of normal 20au; asking for 5 medium globs
|
|
might produce 1 very large glob weighing 600au */
|
|
d.otmp->quan = 1L; /* always 1 for globs */
|
|
d.otmp->owt = weight(d.otmp);
|
|
/* gsize 0: unspecified => small;
|
|
1: small (1..5) => keep default owt for 1, yielding 20;
|
|
2: medium (6..15) => use weight for 6, yielding 120;
|
|
3: large (16..25) => 320; 4: very large (26+) => 520 */
|
|
if (d.gsize > 1)
|
|
d.otmp->owt += ((unsigned) (5 + (d.gsize - 2) * 10)
|
|
* d.otmp->owt); /* 20 + {5|15|25} times 20 */
|
|
/* limit overall weight which limits shrink-away time which in turn
|
|
affects how long some of it will remain available to be eaten */
|
|
if (d.cnt > 1) {
|
|
int rn1cnt = rn1(5, 2); /* 2..6 */
|
|
|
|
if (rn1cnt > 6 - d.gsize)
|
|
rn1cnt = 6 - d.gsize;
|
|
if (d.cnt > rn1cnt
|
|
&& (!wizard || program_state.wizkit_wishing
|
|
|| y_n("Override glob weight limit?") != 'y'))
|
|
d.cnt = rn1cnt;
|
|
d.otmp->owt *= (unsigned) d.cnt;
|
|
}
|
|
/* note: the owt assignment below will not change glob's weight */
|
|
d.cnt = 0;
|
|
} else if (d.cnt > 0) {
|
|
if (objects[d.typ].oc_merge
|
|
&& (wizard /* quantity isn't restricted when debugging */
|
|
/* note: in normal play, explicitly asking for 1 might
|
|
fail the 'cnt < rnd(6)' test and could produce more
|
|
than 1 if mksobj() creates the item that way */
|
|
|| d.cnt < rnd(6)
|
|
|| (d.cnt <= 7 && Is_candle(d.otmp))
|
|
|| (d.cnt <= 20
|
|
&& (d.typ == ROCK || d.typ == FLINT || is_missile(d.otmp)
|
|
/* WEAPON_CLASS test excludes gems, gray stones */
|
|
|| (d.oclass == WEAPON_CLASS && is_ammo(d.otmp))))))
|
|
d.otmp->quan = (long) d.cnt;
|
|
}
|
|
|
|
if (d.islit && (d.typ == OIL_LAMP || d.typ == MAGIC_LAMP
|
|
|| d.typ == BRASS_LANTERN
|
|
|| Is_candle(d.otmp) || d.typ == POT_OIL)) {
|
|
place_object(d.otmp, u.ux, u.uy); /* make it viable light source */
|
|
begin_burn(d.otmp, FALSE);
|
|
obj_extract_self(d.otmp); /* now release it for caller's use */
|
|
}
|
|
|
|
if (d.spesgn == 0) {
|
|
/* spe not specified; retain the randomly assigned value */
|
|
d.spe = d.otmp->spe;
|
|
} else if (wizard) {
|
|
; /* no restrictions except SPE_LIM */
|
|
} else if (d.oclass == ARMOR_CLASS || d.oclass == WEAPON_CLASS
|
|
|| is_weptool(d.otmp)
|
|
|| (d.oclass == RING_CLASS && objects[d.typ].oc_charged)) {
|
|
if (d.spe > rnd(5) && d.spe > d.otmp->spe)
|
|
d.spe = 0;
|
|
if (d.spe > 2 && Luck < 0)
|
|
d.spesgn = -1;
|
|
} else {
|
|
/* crystal ball cancels like a wand, to (n:-1) */
|
|
if (d.oclass == WAND_CLASS || d.typ == CRYSTAL_BALL) {
|
|
if (d.spe > 1 && d.spesgn == -1)
|
|
d.spe = 1;
|
|
} else {
|
|
if (d.spe > 0 && d.spesgn == -1)
|
|
d.spe = 0;
|
|
}
|
|
if (d.spe > d.otmp->spe)
|
|
d.spe = d.otmp->spe;
|
|
}
|
|
|
|
if (d.spesgn == -1)
|
|
d.spe = -d.spe;
|
|
|
|
/* set otmp->spe. This may, or may not, use d.spe... */
|
|
switch (d.typ) {
|
|
case TIN:
|
|
d.otmp->spe = 0; /* default: not spinach */
|
|
if (d.contents == TIN_EMPTY) {
|
|
d.otmp->corpsenm = NON_PM;
|
|
} else if (d.contents == TIN_SPINACH) {
|
|
d.otmp->corpsenm = NON_PM;
|
|
d.otmp->spe = 1; /* spinach after all */
|
|
}
|
|
break;
|
|
case TOWEL:
|
|
if (d.wetness)
|
|
d.otmp->spe = d.wetness;
|
|
break;
|
|
case SLIME_MOLD:
|
|
d.otmp->spe = d.ftype;
|
|
FALLTHROUGH;
|
|
/* FALLTHRU */
|
|
case SKELETON_KEY:
|
|
case CHEST:
|
|
case LARGE_BOX:
|
|
case HEAVY_IRON_BALL:
|
|
case IRON_CHAIN:
|
|
break;
|
|
case STATUE: /* otmp->cobj already done in mksobj() */
|
|
case FIGURINE:
|
|
case CORPSE: {
|
|
struct permonst *P = (ismnum(d.mntmp)) ? &mons[d.mntmp] : 0;
|
|
|
|
d.otmp->spe = !P ? CORPSTAT_RANDOM
|
|
/* if neuter, force neuter regardless of wish request */
|
|
: is_neuter(P) ? CORPSTAT_NEUTER
|
|
/* not neuter, honor wish unless it conflicts */
|
|
: (d.mgend == FEMALE && !is_male(P)) ? CORPSTAT_FEMALE
|
|
: (d.mgend == MALE && !is_female(P)) ? CORPSTAT_MALE
|
|
/* unspecified or wish conflicts */
|
|
: CORPSTAT_RANDOM;
|
|
if (P && d.otmp->spe == CORPSTAT_RANDOM)
|
|
d.otmp->spe = is_male(P) ? CORPSTAT_MALE
|
|
: is_female(P) ? CORPSTAT_FEMALE
|
|
: rn2(2) ? CORPSTAT_MALE : CORPSTAT_FEMALE;
|
|
if (d.ishistoric && d.typ == STATUE)
|
|
d.otmp->spe |= CORPSTAT_HISTORIC;
|
|
break;
|
|
};
|
|
#ifdef MAIL_STRUCTURES
|
|
/* scroll of mail: 0: delivered in-game via external event (or randomly
|
|
for fake mail); 1: from bones or wishing; 2: written with marker */
|
|
case SCR_MAIL:
|
|
d.otmp->spe = 1;
|
|
break;
|
|
#endif
|
|
/* splash of venom: 0: normal, and transitory; 1: wishing */
|
|
case ACID_VENOM:
|
|
case BLINDING_VENOM:
|
|
d.otmp->spe = 1;
|
|
break;
|
|
case WAN_WISHING:
|
|
if (!wizard) {
|
|
d.otmp->spe = (rn2(10) ? -1 : 0);
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
default:
|
|
d.otmp->spe = d.spe;
|
|
}
|
|
|
|
/* set otmp->corpsenm or dragon scale [mail] */
|
|
if (ismnum(d.mntmp)) {
|
|
int humanwere;
|
|
|
|
if (d.mntmp == PM_LONG_WORM_TAIL)
|
|
d.mntmp = PM_LONG_WORM;
|
|
/* werecreatures in beast form are all flagged no-corpse so for
|
|
corpses and tins, switch to their corresponding human form;
|
|
for figurines, override the can't-be-human restriction instead */
|
|
if (d.typ != FIGURINE && is_were(&mons[d.mntmp])
|
|
&& (svm.mvitals[d.mntmp].mvflags & G_NOCORPSE) != 0
|
|
&& (humanwere = counter_were(d.mntmp)) != NON_PM)
|
|
d.mntmp = humanwere;
|
|
|
|
switch (d.typ) {
|
|
case TIN:
|
|
if (dead_species(d.mntmp, FALSE)) {
|
|
d.otmp->corpsenm = NON_PM; /* it's empty */
|
|
} else if ((!(mons[d.mntmp].geno & G_UNIQ) || wizard)
|
|
&& !(svm.mvitals[d.mntmp].mvflags & G_NOCORPSE)
|
|
&& mons[d.mntmp].cnutrit != 0) {
|
|
d.otmp->corpsenm = d.mntmp;
|
|
}
|
|
break;
|
|
case CORPSE:
|
|
if ((!(mons[d.mntmp].geno & G_UNIQ) || wizard)
|
|
&& !(svm.mvitals[d.mntmp].mvflags & G_NOCORPSE)) {
|
|
if (mons[d.mntmp].msound == MS_GUARDIAN)
|
|
d.mntmp = genus(d.mntmp, 1);
|
|
set_corpsenm(d.otmp, d.mntmp);
|
|
}
|
|
if (d.zombify && zombie_form(&mons[d.mntmp])) {
|
|
(void) start_timer(rn1(5, 10), TIMER_OBJECT,
|
|
ZOMBIFY_MON, obj_to_any(d.otmp));
|
|
}
|
|
break;
|
|
case EGG:
|
|
d.mntmp = can_be_hatched(d.mntmp);
|
|
/* this also sets hatch timer if appropriate */
|
|
set_corpsenm(d.otmp, d.mntmp);
|
|
break;
|
|
case FIGURINE:
|
|
if (!(mons[d.mntmp].geno & G_UNIQ)
|
|
&& (!is_human(&mons[d.mntmp]) || is_were(&mons[d.mntmp]))
|
|
#ifdef MAIL_STRUCTURES
|
|
&& d.mntmp != PM_MAIL_DAEMON
|
|
#endif
|
|
)
|
|
d.otmp->corpsenm = d.mntmp;
|
|
break;
|
|
case STATUE:
|
|
d.otmp->corpsenm = d.mntmp;
|
|
if (Has_contents(d.otmp) && verysmall(&mons[d.mntmp]))
|
|
delete_contents(d.otmp); /* no spellbook */
|
|
break;
|
|
case SCALE_MAIL:
|
|
/* Dragon mail - depends on the order of objects & dragons. */
|
|
if (d.mntmp >= PM_GRAY_DRAGON && d.mntmp <= PM_YELLOW_DRAGON)
|
|
d.otmp->otyp = GRAY_DRAGON_SCALE_MAIL
|
|
+ d.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 (d.iscursed) {
|
|
curse(d.otmp);
|
|
} else if (d.uncursed) {
|
|
d.otmp->blessed = 0;
|
|
d.otmp->cursed = (Luck < 0 && !wizard);
|
|
} else if (d.blessed) {
|
|
d.otmp->blessed = (Luck >= 0 || wizard);
|
|
d.otmp->cursed = (Luck < 0 && !wizard);
|
|
} else if (d.spesgn < 0) {
|
|
curse(d.otmp);
|
|
}
|
|
|
|
/* set eroded and erodeproof */
|
|
if (erosion_matters(d.otmp)) {
|
|
/* wished-for item shouldn't be eroded unless specified */
|
|
d.otmp->oeroded = d.otmp->oeroded2 = 0;
|
|
if (d.eroded && (is_flammable(d.otmp) || is_rustprone(d.otmp)
|
|
|| is_crackable(d.otmp)))
|
|
d.otmp->oeroded = d.eroded;
|
|
if (d.eroded2 && (is_corrodeable(d.otmp) || is_rottable(d.otmp)))
|
|
d.otmp->oeroded2 = d.eroded2;
|
|
/*
|
|
* 3.6.1: earlier versions included `&& !eroded && !eroded2' here,
|
|
* but damageproof combined with damaged is feasible (eroded
|
|
* armor modified by confused reading of cursed destroy armor)
|
|
* so don't prevent player from wishing for such a combination.
|
|
*/
|
|
if (d.erodeproof
|
|
&& (is_damageable(d.otmp) || d.otmp->otyp == CRYSKNIFE))
|
|
d.otmp->oerodeproof = (Luck >= 0 || wizard);
|
|
}
|
|
|
|
/* set otmp->recharged */
|
|
if (d.oclass == WAND_CLASS) {
|
|
/* prevent wishing abuse */
|
|
if (d.otmp->otyp == WAN_WISHING && !wizard)
|
|
d.rechrg = 1;
|
|
d.otmp->recharged = (unsigned) d.rechrg;
|
|
}
|
|
|
|
/* set poisoned */
|
|
if (d.ispoisoned) {
|
|
if (is_poisonable(d.otmp))
|
|
d.otmp->opoisoned = (Luck >= 0);
|
|
else if (d.oclass == FOOD_CLASS)
|
|
/* try to taint by making it as old as possible */
|
|
d.otmp->age = 1L;
|
|
}
|
|
/* and [un]trapped */
|
|
if (d.trapped) {
|
|
if (Is_box(d.otmp) || d.typ == TIN)
|
|
d.otmp->otrapped = (d.trapped == 1);
|
|
}
|
|
/* empty for containers rather than for tins */
|
|
if (d.contents == TIN_EMPTY) {
|
|
if (d.otmp->otyp == BAG_OF_TRICKS || d.otmp->otyp == HORN_OF_PLENTY) {
|
|
if (d.otmp->spe > 0)
|
|
d.otmp->spe = 0;
|
|
} else if (Has_contents(d.otmp)) {
|
|
/* this assumes that artifacts can't be randomly generated
|
|
inside containers */
|
|
delete_contents(d.otmp);
|
|
d.otmp->owt = weight(d.otmp);
|
|
}
|
|
}
|
|
/* set locked/unlocked/broken */
|
|
if (Is_box(d.otmp)) {
|
|
if (d.locked) {
|
|
d.otmp->olocked = 1, d.otmp->obroken = 0;
|
|
} else if (d.unlocked) {
|
|
d.otmp->olocked = 0, d.otmp->obroken = 0;
|
|
} else if (d.broken) {
|
|
d.otmp->olocked = 0, d.otmp->obroken = 1;
|
|
}
|
|
if (d.otmp->obroken)
|
|
d.otmp->otrapped = 0;
|
|
}
|
|
|
|
if (d.isgreased)
|
|
d.otmp->greased = 1;
|
|
|
|
if (d.isdiluted && d.otmp->oclass == POTION_CLASS)
|
|
d.otmp->odiluted = (d.otmp->otyp != POT_WATER);
|
|
|
|
/* set tin variety */
|
|
if (d.otmp->otyp == TIN && d.tvariety >= 0 && (rn2(4) || wizard))
|
|
set_tin_variety(d.otmp, d.tvariety);
|
|
|
|
if (d.name) {
|
|
const char *aname, *novelname;
|
|
short objtyp;
|
|
|
|
/* an artifact name might need capitalization fixing */
|
|
aname = artifact_name(d.name, &objtyp, TRUE);
|
|
if (aname && objtyp == d.otmp->otyp)
|
|
d.name = aname;
|
|
|
|
/* 3.6 tribute - fix up novel */
|
|
if (d.otmp->otyp == SPE_NOVEL
|
|
&& (novelname = lookup_novel(d.name, &d.otmp->novelidx)) != 0)
|
|
d.name = novelname;
|
|
|
|
d.otmp = oname(d.otmp, d.name, ONAME_WISH);
|
|
/* name==aname => wished for artifact (otmp->oartifact => got it) */
|
|
if (d.otmp->oartifact || d.name == aname) {
|
|
d.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(d.otmp)
|
|
|| (d.otmp->oartifact && rn2(nartifact_exist()) > 1)) && !wizard) {
|
|
artifact_exists(d.otmp, safe_oname(d.otmp), FALSE, ONAME_NO_FLAGS);
|
|
obfree(d.otmp, (struct obj *) 0);
|
|
d.otmp = &hands_obj;
|
|
pline("For a moment, you feel %s in your %s, but it disappears!",
|
|
something, makeplural(body_part(HAND)));
|
|
return d.otmp;
|
|
}
|
|
|
|
if (d.halfeaten && d.otmp->oclass == FOOD_CLASS) {
|
|
unsigned nut = obj_nutrition(d.otmp);
|
|
|
|
/* do this adjustment before setting up object's weight; skip
|
|
"partly eaten" for food with 0 nutrition (wraith corpse) or for
|
|
anything that couldn't take more than one bite (1 nutrition;
|
|
ought to check for one-bite instead but that's complicated) */
|
|
if (nut > 1) {
|
|
d.otmp->oeaten = nut;
|
|
consume_oeaten(d.otmp, 1);
|
|
}
|
|
}
|
|
d.otmp->owt = weight(d.otmp);
|
|
if (d.very && d.otmp->otyp == HEAVY_IRON_BALL)
|
|
d.otmp->owt += IRON_BALL_W_INCR;
|
|
|
|
return d.otmp;
|
|
}
|
|
|
|
int
|
|
rnd_class(int first, int last)
|
|
{
|
|
int i, x, sum = 0;
|
|
|
|
if (last > first) {
|
|
for (i = first; i <= last; i++)
|
|
sum += objects[i].oc_prob;
|
|
if (!sum) /* all zero, so equal probability */
|
|
return rn1(last - first + 1, first);
|
|
|
|
x = rnd(sum);
|
|
for (i = first; i <= last; i++)
|
|
if ((x -= objects[i].oc_prob) <= 0)
|
|
return i;
|
|
}
|
|
return (first == last) ? first : STRANGE_OBJECT;
|
|
}
|
|
|
|
const char *
|
|
Japanese_item_name(int i, const char *ordinaryname)
|
|
{
|
|
const struct Jitem *j = Japanese_items;
|
|
|
|
while (j->item) {
|
|
if (i == j->item)
|
|
return j->name;
|
|
j++;
|
|
}
|
|
return ordinaryname;
|
|
}
|
|
|
|
const char *
|
|
armor_simple_name(struct obj *armor)
|
|
{
|
|
const char *result = 0;
|
|
unsigned armcat = objects[armor->otyp].oc_armcat;
|
|
|
|
switch (armcat) {
|
|
case ARM_SUIT:
|
|
result = suit_simple_name(armor);
|
|
break;
|
|
case ARM_CLOAK:
|
|
result = cloak_simple_name(armor);
|
|
break;
|
|
case ARM_HELM:
|
|
result = helm_simple_name(armor);
|
|
break;
|
|
case ARM_GLOVES:
|
|
result = gloves_simple_name(armor);
|
|
break;
|
|
case ARM_BOOTS:
|
|
result = boots_simple_name(armor);
|
|
break;
|
|
case ARM_SHIELD:
|
|
result = shield_simple_name(armor);
|
|
break;
|
|
case ARM_SHIRT:
|
|
result = shirt_simple_name(armor);
|
|
break;
|
|
default:
|
|
result = simpleonames(armor);
|
|
impossible("unknown armor category (%s => %u)", result, armcat);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const char *
|
|
suit_simple_name(struct obj *suit)
|
|
{
|
|
const char *suitnm, *esuitp;
|
|
|
|
if (suit) {
|
|
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(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(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 !hard_helmet(helmet) ? "hat" : "helm";
|
|
}
|
|
|
|
/* gloves vs gauntlets; depends upon discovery state */
|
|
const char *
|
|
gloves_simple_name(struct obj *gloves)
|
|
{
|
|
static const char gauntlets[] = "gauntlets";
|
|
|
|
if (gloves && gloves->dknown) {
|
|
int otyp = gloves->otyp;
|
|
struct objclass *ocl = &objects[otyp];
|
|
const char *actualn = OBJ_NAME(*ocl),
|
|
*descrpn = OBJ_DESCR(*ocl);
|
|
|
|
if (strstri(objects[otyp].oc_name_known ? actualn : descrpn,
|
|
gauntlets))
|
|
return gauntlets;
|
|
}
|
|
return "gloves";
|
|
}
|
|
|
|
/* boots vs shoes; depends upon discovery state */
|
|
const char *
|
|
boots_simple_name(struct obj *boots)
|
|
{
|
|
static const char shoes[] = "shoes";
|
|
|
|
if (boots && boots->dknown) {
|
|
int otyp = boots->otyp;
|
|
struct objclass *ocl = &objects[otyp];
|
|
const char *actualn = OBJ_NAME(*ocl),
|
|
*descrpn = OBJ_DESCR(*ocl);
|
|
|
|
if (strstri(descrpn, shoes)
|
|
|| (objects[otyp].oc_name_known && strstri(actualn, shoes)))
|
|
return shoes;
|
|
}
|
|
return "boots";
|
|
}
|
|
|
|
/* simplified shield for messages */
|
|
const char *
|
|
shield_simple_name(struct obj *shield)
|
|
{
|
|
if (shield) {
|
|
/* xname() describes unknown (unseen) reflection as smooth */
|
|
if (shield->otyp == SHIELD_OF_REFLECTION)
|
|
return shield->dknown ? "silver shield" : "smooth shield";
|
|
/*
|
|
* We might distinguish between wooden vs metallic or
|
|
* light vs heavy to give small benefit to spell casters.
|
|
* Fighter types probably care more about the former for
|
|
* vulnerability to fire or rust.
|
|
*
|
|
* We could do that both ways: light wooden shield, light
|
|
* metallic shield (there aren't any), heavy wooden shield,
|
|
* and heavy metallic shield but that's getting away from
|
|
* "simple name" which is intended to be shorter as well
|
|
* as less detailed than xname().
|
|
*/
|
|
#if 0
|
|
/* spellcasting uses a division like this */
|
|
return (weight(shield) > (int) objects[SMALL_SHIELD].oc_weight)
|
|
? "heavy shield"
|
|
: "light shield";
|
|
#endif
|
|
}
|
|
return "shield";
|
|
}
|
|
|
|
/* for completeness */
|
|
const char *
|
|
shirt_simple_name(struct obj *shirt UNUSED)
|
|
{
|
|
return "shirt";
|
|
}
|
|
|
|
const char *
|
|
mimic_obj_name(struct monst *mtmp)
|
|
{
|
|
if (M_AP_TYPE(mtmp) == M_AP_OBJECT) {
|
|
if (mtmp->mappearance == GOLD_PIECE)
|
|
return "gold";
|
|
if (mtmp->mappearance != STRANGE_OBJECT)
|
|
return simple_typename(mtmp->mappearance);
|
|
}
|
|
return "whatcha-may-callit";
|
|
}
|
|
|
|
/*
|
|
* Construct a query prompt string, based around an object name, which is
|
|
* guaranteed to fit within [QBUFSZ]. Takes an optional prefix, three
|
|
* choices for filling in the middle (two object formatting functions and a
|
|
* last resort literal which should be very short), and an optional suffix.
|
|
*/
|
|
char *
|
|
safe_qbuf(
|
|
char *qbuf, /* output buffer */
|
|
const char *qprefix,
|
|
const char *qsuffix,
|
|
struct obj *obj,
|
|
char *(*func)(OBJ_P),
|
|
char *(*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;
|
|
assert(endp != NULL); /* workaround for static analyzer issue */
|
|
/* 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*/
|