makeplural/makesingular overhaul redux (trunk only)

Move some of the singular<->plural transformations from code to data.
Also fixes one more missed singularization:  lurkers above.  A big chunk
of this is just a bit of minor reorganization:  moving otense() & vtense()
next to makeplural(), and moving the wishable subranges array from between
makeplural & makesingular to in front of the wishing code.

     I was going to redo makeplural to use the same style as makesingular
(switch from ``len >= N && !strcmpi(buf, spot-(N-1))'', with spot pointing
at final character, over to ``BSTRCMPI(bp, p-N)'' which tests p-N against
bp as the bounds check and has p pointing to the string's terinating '\0')
but have decided not to tackle that.
This commit is contained in:
nethack.rankin
2007-05-05 01:31:47 +00:00
parent 15e79e99b9
commit befae3f110

View File

@@ -1350,134 +1350,6 @@ register const char *verb;
return(bp);
}
/* return form of the verb (input plural) if xname(otmp) were the subject */
char *
otense(otmp, verb)
register struct obj *otmp;
register const char *verb;
{
char *buf;
/*
* verb is given in plural (without trailing s). Return as input
* if the result of xname(otmp) would be plural. Don't bother
* recomputing xname(otmp) at this time.
*/
if (!is_plural(otmp))
return vtense((char *)0, verb);
buf = nextobuf();
Strcpy(buf, verb);
return buf;
}
/* various singular words that vtense would otherwise categorize as plural */
static const char * const special_subjs[] = {
"erinys",
"manes", /* this one is ambiguous */
"Cyclops",
"Hippocrates",
"Pelias",
"aklys",
"amnesia",
"paralysis",
0
};
/* return form of the verb (input plural) for present tense 3rd person subj */
char *
vtense(subj, verb)
register const char *subj;
register const char *verb;
{
char *buf = nextobuf(), *bspot;
int len, ltmp;
const char *sp, *spot;
const char * const *spec;
/*
* verb is given in plural (without trailing s). Return as input
* if subj appears to be plural. Add special cases as necessary.
* Many hard cases can already be handled by using otense() instead.
* If this gets much bigger, consider decomposing makeplural.
* Note: monster names are not expected here (except before corpse).
*
* special case: allow null sobj to get the singular 3rd person
* present tense form so we don't duplicate this code elsewhere.
*/
if (subj) {
if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3))
goto sing;
spot = (const char *)0;
for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) {
if (!strncmpi(sp, " of ", 4) ||
!strncmpi(sp, " from ", 6) ||
!strncmpi(sp, " called ", 8) ||
!strncmpi(sp, " named ", 7) ||
!strncmpi(sp, " labeled ", 9)) {
if (sp != subj) spot = sp - 1;
break;
}
}
len = (int) strlen(subj);
if (!spot) spot = subj + len - 1;
/*
* plural: anything that ends in 's', but not '*us' or '*ss'.
* Guess at a few other special cases that makeplural creates.
*/
if ((lowc(*spot) == 's' && spot != subj &&
!index("us", lowc(*(spot-1)))) ||
BSTRNCMPI(subj, spot-3, "eeth", 4) ||
BSTRNCMPI(subj, spot-3, "feet", 4) ||
BSTRNCMPI(subj, spot-1, "ia", 2) ||
BSTRNCMPI(subj, spot-1, "ae", 2)) {
/* check for special cases to avoid false matches */
len = (int)(spot - subj) + 1;
for (spec = special_subjs; *spec; spec++) {
ltmp = strlen(*spec);
if (len == ltmp && !strncmpi(*spec, subj, len)) goto sing;
/* also check for <prefix><space><special_subj>
to catch things like "the invisible erinys" */
if (len > ltmp && *(spot - ltmp) == ' ' &&
!strncmpi(*spec, spot - ltmp + 1, ltmp)) goto sing;
}
return strcpy(buf, verb);
}
/*
* 3rd person plural doesn't end in telltale 's';
* 2nd person singular behaves as if plural.
*/
if (!strcmpi(subj, "they") || !strcmpi(subj, "you"))
return strcpy(buf, verb);
}
sing:
Strcpy(buf, verb);
len = (int)strlen(buf);
bspot = buf + len - 1;
if (!strcmpi(buf, "are")) {
Strcasecpy(buf, "is");
} else if (!strcmpi(buf, "have")) {
Strcasecpy(bspot-1, "s");
} else if (index("zxs", lowc(*bspot)) ||
(len >= 2 && lowc(*bspot) == 'h' &&
index("cs", lowc(*(bspot-1)))) ||
(len == 2 && lowc(*bspot) == 'o')) {
/* Ends in z, x, s, ch, sh; add an "es" */
Strcasecpy(bspot+1, "es");
} else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot-1)))) {
/* like "y" case in makeplural */
Strcasecpy(bspot, "ies");
} else {
Strcasecpy(bspot+1, "s");
}
return buf;
}
/* capitalized variant of doname() */
char *
Doname2(obj)
@@ -1618,6 +1490,189 @@ static const char wrpsym[] = {
FOOD_CLASS
};
/* return form of the verb (input plural) if xname(otmp) were the subject */
char *
otense(otmp, verb)
register struct obj *otmp;
register const char *verb;
{
char *buf;
/*
* verb is given in plural (without trailing s). Return as input
* if the result of xname(otmp) would be plural. Don't bother
* recomputing xname(otmp) at this time.
*/
if (!is_plural(otmp))
return vtense((char *)0, verb);
buf = nextobuf();
Strcpy(buf, verb);
return buf;
}
/* various singular words that vtense would otherwise categorize as plural;
also used by makesingular() to catch some special cases */
static const char * const special_subjs[] = {
"erinys",
"manes", /* this one is ambiguous */
"Cyclops",
"Hippocrates",
"Pelias",
"aklys",
"amnesia",
"detect monsters",
"paralysis",
"shape changers",
"nemesis",
0
/* note: "detect monsters" and "shape changers" are normally
caught via "<something>(s) of <whatever>", but they can be
wished for using the shorter form, so we include them here
to accomodate usage by makesingular during wishing */
};
/* return form of the verb (input plural) for present tense 3rd person subj */
char *
vtense(subj, verb)
register const char *subj;
register const char *verb;
{
char *buf = nextobuf(), *bspot;
int len, ltmp;
const char *sp, *spot;
const char * const *spec;
/*
* verb is given in plural (without trailing s). Return as input
* if subj appears to be plural. Add special cases as necessary.
* Many hard cases can already be handled by using otense() instead.
* If this gets much bigger, consider decomposing makeplural.
* Note: monster names are not expected here (except before corpse).
*
* Special case: allow null sobj to get the singular 3rd person
* present tense form so we don't duplicate this code elsewhere.
*/
if (subj) {
if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3))
goto sing;
spot = (const char *)0;
for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) {
if (!strncmpi(sp, " of ", 4) ||
!strncmpi(sp, " from ", 6) ||
!strncmpi(sp, " called ", 8) ||
!strncmpi(sp, " named ", 7) ||
!strncmpi(sp, " labeled ", 9)) {
if (sp != subj) spot = sp - 1;
break;
}
}
len = (int) strlen(subj);
if (!spot) spot = subj + len - 1;
/*
* plural: anything that ends in 's', but not '*us' or '*ss'.
* Guess at a few other special cases that makeplural creates.
*/
if ((lowc(*spot) == 's' && spot != subj &&
!index("us", lowc(*(spot-1)))) ||
BSTRNCMPI(subj, spot-3, "eeth", 4) ||
BSTRNCMPI(subj, spot-3, "feet", 4) ||
BSTRNCMPI(subj, spot-1, "ia", 2) ||
BSTRNCMPI(subj, spot-1, "ae", 2)) {
/* check for special cases to avoid false matches */
len = (int)(spot - subj) + 1;
for (spec = special_subjs; *spec; spec++) {
ltmp = strlen(*spec);
if (len == ltmp && !strncmpi(*spec, subj, len)) goto sing;
/* also check for <prefix><space><special_subj>
to catch things like "the invisible erinys" */
if (len > ltmp && *(spot - ltmp) == ' ' &&
!strncmpi(*spec, spot - ltmp + 1, ltmp)) goto sing;
}
return strcpy(buf, verb);
}
/*
* 3rd person plural doesn't end in telltale 's';
* 2nd person singular behaves as if plural.
*/
if (!strcmpi(subj, "they") || !strcmpi(subj, "you"))
return strcpy(buf, verb);
}
sing:
Strcpy(buf, verb);
len = (int)strlen(buf);
bspot = buf + len - 1;
if (!strcmpi(buf, "are")) {
Strcasecpy(buf, "is");
} else if (!strcmpi(buf, "have")) {
Strcasecpy(bspot-1, "s");
} else if (index("zxs", lowc(*bspot)) ||
(len >= 2 && lowc(*bspot) == 'h' &&
index("cs", lowc(*(bspot-1)))) ||
(len == 2 && lowc(*bspot) == 'o')) {
/* Ends in z, x, s, ch, sh; add an "es" */
Strcasecpy(bspot+1, "es");
} else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot-1)))) {
/* like "y" case in makeplural */
Strcasecpy(bspot, "ies");
} else {
Strcasecpy(bspot+1, "s");
}
return buf;
}
struct sing_plur {
const char *sing, *plur;
};
/* word pairs that don't fit into formula-based transformations;
also some suffices which have very few--often one--matches or
which aren't systematically reversible (knives, staves) */
static struct sing_plur one_off[] = {
{ "child", "children" }, /* (for wise guys who give their food funny names) */
{ "cubus", "cubi" }, /* in-/suc-cubus */
{ "culus", "culi" }, /* homunculus */
{ "djinni", "djinn" },
{ "erinys", "erinyes" },
{ "foot", "feet" },
{ "fungus", "fungi" },
{ "knife", "knives" },
{ "louse", "lice" },
{ "mouse", "mice" },
{ "mumak", "mumakil" },
{ "nemesis", "nemeses" },
{ "rtex", "rtices" }, /* vortex */
{ "tooth", "teeth" },
{ "staff", "staves" },
{ 0, 0 }
};
static const char *const as_is[] = {
/* makesingular() leaves these plural due to how they're used */
"boots", "shoes",
"gloves", "lenses", "scales",
"gauntlets",
#ifdef WIZARD
"iron bars",
#endif
/* both singular and plural are spelled the same */
"deer", "fish", "tuna", "yaki",
"krill", "manes", "ninja", "sheep", "ronin", "roshi", "shito", "tengu",
"ki-rin", "Nazgul",
"gunyoki", "pirahna",
"shuriken",
0,
/* Note: "fish" and "pirahna" are collective plurals, suitable
for "wiped out all <foo>". For "3 <foo>", they should be
"fishes" and "pirahnas" instead. We settle for collective
variant instead of attempting to support both. */
};
/* Plural routine; chiefly used for user-defined fruits. We have to try to
* account for everything reasonable the player has; something unreasonable
* can still break the code. However, it's still a lot more accurate than
@@ -1661,6 +1716,7 @@ const char *oldstr;
/* Search for common compounds, ex. lump of royal jelly */
for (spot = str; *spot; spot++) {
if (!index(" -", *spot)) continue;
if (!strncmpi(spot, " of ", 4) ||
!strncmpi(spot, " labeled ", 9) ||
!strncmpi(spot, " called ", 8) ||
@@ -1695,27 +1751,15 @@ const char *oldstr;
goto bottom;
}
/* dispense with some words which don't need pluralization */
{
static const char *const as_is[] = {
/* already plural and/or makesingular() leaves untouched */
static const char *const already_plural[] = {
"men", /* also catches women, watchmen */
"cubi", "culi", "feet", /* in-/suc-cubi, homun-culi */
"algae", "boots", "djinn", "fungi", "shoes", "teeth",
"gloves", "lenses", "matzot", "rtices", "scales",
"erinyes", "mumakil", "nemeses",
"children",
"gauntlets",
#ifdef WIZARD
"iron bars",
#endif
/* both singular and plural are spelled the same */
"deer", "fish", "tuna", "yaki",
"manes", "ninja", "sheep", "ronin", "roshi", "shito", "tengu",
"ki-rin", "Nazgul",
"gunyoki",
"shuriken",
"algae",
"matzot",
0,
};
const struct sing_plur *sp;
const char *const *as;
int al;
@@ -1724,12 +1768,28 @@ const char *oldstr;
if (len >= al && !strcmpi(spot - (al - 1), *as))
goto bottom;
}
for (as = already_plural; *as; ++as) {
al = (int)strlen(*as);
if (len >= al && !strcmpi(spot - (al - 1), *as))
goto bottom;
}
/* more of same, but not suitable for blanket loop checking */
if ((len == 2 && !strcmpi(str, "ya")) ||
(len >= 2 && !strcmpi(spot-1, "ai")) || /* samurai, Uruk-hai */
(len >= 3 && !strcmpi(spot-2, " ya")))
goto bottom;
for (sp = one_off; sp->plur; sp++) {
al = (int)strlen(sp->plur);
if (len >= al && !strcmpi(spot - (al - 1), sp->plur))
goto bottom; /* already plural */
al = (int)strlen(sp->sing);
if (len >= al && !strcmpi(spot - (al - 1), sp->sing)) {
Strcasecpy(spot - (al - 1), sp->plur);
goto bottom; /* one_off[] plural */
}
}
}
/* man/men ("Wiped out all cavemen.") */
@@ -1740,19 +1800,7 @@ const char *oldstr;
Strcasecpy(spot-1, "en");
goto bottom;
}
/* tooth/teeth */
if (len >= 5 && !strcmpi(spot-4, "tooth")) {
Strcasecpy(spot-3, "eeth");
goto bottom;
}
/* knife/knives, etc..., staff/staves */
if (!strcmpi(spot-1, "fe") ||
(!strcmpi(spot-1, "ff") &&
(len >= 5 && !strncmpi(spot-4, "staf", 4)))) {
/* fe or ff to ves */
Strcasecpy(spot-1, "ves");
goto bottom;
} else if (lowc(*spot) == 'f') {
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" */
@@ -1763,11 +1811,6 @@ const char *oldstr;
goto bottom;
}
}
/* foot/feet (body part) */
if (len >= 4 && !strcmpi(spot-3, "foot")) {
Strcasecpy(spot-2, "eet");
goto bottom;
}
/* ium/ia (mycelia, baluchitheria) */
if (len >= 3 && !strcmpi(spot-2, "ium")) {
Strcasecpy(spot-2, "ia");
@@ -1776,7 +1819,8 @@ const char *oldstr;
/* algae, larvae, hyphae (another fungus part) */
if ((len >= 4 && !strcmpi(spot-3, "alga")) ||
(len >= 5 && (!strcmpi(spot-4, "hypha") ||
!strcmpi(spot-4, "larva")))) {
!strcmpi(spot-4, "larva"))) ||
(len >= 8 && (!strcmpi(spot-7, "vertebra")))) {
/* a to ae */
Strcasecpy(spot+1, "e");
goto bottom;
@@ -1788,37 +1832,11 @@ const char *oldstr;
Strcasecpy(spot-1, "i");
goto bottom;
}
/* vortex/vortices */
if (len >= 6 && !strcmpi(spot-3, "rtex")) {
Strcasecpy(spot-1, "ices"); /* ex -> ices */
goto bottom;
}
/* djinni/djinn (note: also efreeti/efreet) */
if (len >= 6 && !strcmpi(spot-5, "djinni")) {
*spot = '\0'; /* drop 'i' */
goto bottom;
}
/* mumak/mumakil */
if (len >= 5 && !strcmpi(spot-4, "mumak")) {
Strcasecpy(spot+1, "il");
goto bottom;
}
/* sis/ses (nemesis) */
if (len >= 3 && !strcmpi(spot-2, "sis")) {
Strcasecpy(spot-1, "es");
goto bottom;
}
/* erinys/erinyes */
if (len >= 6 && !strcmpi(spot-5, "erinys")) {
Strcasecpy(spot, "es"); /* s -> es */
goto bottom;
}
/* mouse/mice,louse/lice (not a monster, but possible in food names) */
if (len >= 5 && !strcmpi(spot-3, "ouse") &&
index("ml", lowc(*(spot-4)))) {
Strcasecpy(spot-3, "ice");
goto bottom;
}
/* matzoh/matzot, possible food name */
if (len >= 6 && (!strcmpi(spot-5, "matzoh") ||
!strcmpi(spot-5, "matzah"))) {
@@ -1830,11 +1848,6 @@ const char *oldstr;
Strcasecpy(spot, "ot"); /* o/a -> ot */
goto bottom;
}
/* child/children (for wise guys who give their food funny names) */
if (len >= 5 && !strcmpi(spot-4, "child")) {
Strcasecpy(spot+1, "ren");
goto bottom;
}
/* note: -eau/-eaux (gateau, bordeau...) */
/* note: ox/oxen, VAX/VAXen, goose/geese */
@@ -1864,47 +1877,13 @@ const char *oldstr;
return str;
}
struct o_range {
const char *name, oclass;
int f_o_range, l_o_range;
};
/* wishable subranges of objects */
STATIC_OVL NEARDATA const struct o_range o_ranges[] = {
{ "bag", TOOL_CLASS, SACK, BAG_OF_TRICKS },
{ "lamp", TOOL_CLASS, OIL_LAMP, MAGIC_LAMP },
{ "candle", TOOL_CLASS, TALLOW_CANDLE, WAX_CANDLE },
{ "horn", TOOL_CLASS, TOOLED_HORN, HORN_OF_PLENTY },
{ "shield", ARMOR_CLASS, SMALL_SHIELD, SHIELD_OF_REFLECTION },
{ "hat", ARMOR_CLASS, FEDORA, DUNCE_CAP },
{ "helm", ARMOR_CLASS, ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY },
{ "gloves", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY },
{ "gauntlets", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY },
{ "boots", ARMOR_CLASS, LOW_BOOTS, LEVITATION_BOOTS },
{ "shoes", ARMOR_CLASS, LOW_BOOTS, IRON_SHOES },
{ "cloak", ARMOR_CLASS, MUMMY_WRAPPING, CLOAK_OF_DISPLACEMENT },
#ifdef TOURIST
{ "shirt", ARMOR_CLASS, HAWAIIAN_SHIRT, T_SHIRT },
#endif
{ "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 },
#ifdef WIZARD
{ "venom", VENOM_CLASS, BLINDING_VENOM, ACID_VENOM },
#endif
{ "gray stone", GEM_CLASS, LUCKSTONE, FLINT },
{ "grey stone", GEM_CLASS, LUCKSTONE, FLINT },
};
/*
* 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)?
* We assume the latter since it doesn't require any extra handling.
* Its inclusion in 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.
@@ -1932,9 +1911,10 @@ const char *oldstr;
removing "es" rather than just "s" */
if ((p = strstri(bp, " of ")) != 0 ||
(p = strstri(bp, "-in-")) != 0 ||
(p = strstri(bp, "-at-")) != 0) {
(p = strstri(bp, "-at-")) != 0 ||
(p = strstri(bp, " above")) != 0) { /* lurkers above */
/* [wo]men-at-arms -> [wo]man-at-arms; takes "not end in s" exit */
if (!BSTRNCMPI(bp, p-3, "men", 3)) p[-2] = chrcasecpy(p[-2], 'a');
if (!BSTRNCMPI(bp, p-3, "men", 3)) *(p-2) = chrcasecpy(*(p-2), 'a');
if (BSTRNCMPI(bp, p-1, "s", 1)) return bp; /* wasn't plural */
--p; /* back up to the 's' */
@@ -1946,8 +1926,37 @@ const char *oldstr;
return bp;
}
/* remove -s or -es (boxes) or -ies (rubies) */
p = eos(bp);
/* dispense with some words which don't need singularization */
{
const struct sing_plur *sp;
const char *const *as;
int al;
for (as = as_is; *as; ++as) {
al = (int)strlen(*as);
if (!BSTRCMPI(bp, p - al, *as))
return bp;
}
for (as = special_subjs; *as; ++as) {
al = (int)strlen(*as);
if (!BSTRCMPI(bp, p - al, *as))
return bp;
}
for (sp = one_off; sp->sing; sp++) {
al = (int)strlen(sp->sing);
if (!BSTRCMPI(bp, p - al, sp->sing))
return bp; /* already singular */
al = (int)strlen(sp->plur);
if (!BSTRCMPI(bp, p - al, sp->plur)) {
Strcasecpy(p - al, sp->sing);
return bp; /* one_off[] singular */
}
}
}
/* 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" */
@@ -1959,21 +1968,6 @@ const char *oldstr;
Strcasecpy(p-3, "y"); /* ies -> y */
return bp;
}
/* keep as-is */
if (!BSTRCMPI(bp, p-6, "gloves") ||
!BSTRCMPI(bp, p-6, "lenses") ||
!BSTRCMPI(bp, p-6, "scales") ||
!BSTRCMPI(bp, p-5, "shoes"))
return bp;
/* note: cloves / knives from clove / knife */
if (!BSTRCMPI(bp, p-6, "knives")) {
Strcasecpy(p-3, "fe"); /* ves -> fe */
return bp;
}
if (!BSTRCMPI(bp, p-6, "staves")) {
Strcasecpy(p-3, "ff"); /* ves -> ff */
return bp;
}
/* wolves, but f to ves isn't fully reversible */
if (p-4 >= bp && (index("lr", lowc(*(p-4))) ||
index(vowels, lowc(*(p-4)))) &&
@@ -1983,12 +1977,9 @@ const char *oldstr;
Strcasecpy(p-3, "f"); /* ves -> f */
return bp;
}
if (!BSTRCMPI(bp, p-8, "vortices")) {
Strcasecpy(p-4, "ex"); /* ices -> ex */
return bp;
}
/* note: nurses, axes but boxes, wumpuses */
if (!BSTRCMPI(bp, p-4, "oxes") || /* boxes, foxes */
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 */
@@ -1998,84 +1989,30 @@ const char *oldstr;
!BSTRCMPI(bp, p-7, "Aleaxes")) {
*(p-2) = '\0'; /* drop es */
return bp;
}
if (!BSTRCMPI(bp, p-7, "nemeses")) {
Strcasecpy(p-2, "is"); /* es -> is */
return bp;
}
if (!BSTRCMPI(bp, p-7, "erinyes")) {
Strcasecpy(p-2, "s"); /* es -> s */
return bp;
}
} /* 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"))
return bp;
} else if (!BSTRCMPI(bp, p-5, "boots") ||
!BSTRCMPI(bp, p-9, "gauntlets") ||
!BSTRCMPI(bp, p-6, "tricks") ||
!BSTRCMPI(bp, p-6, "erinys") ||
!BSTRCMPI(bp, p-7, "nemesis") ||
!BSTRCMPI(bp, p-9, "paralysis") ||
!BSTRCMPI(bp, p-5, "glass") ||
} else if (!BSTRCMPI(bp, p-2, "ss") ||
!BSTRCMPI(bp, p-5, " lens") ||
(p-4 == bp && !strcmpi(p-4, "lens")) ||
!BSTRCMPI(bp, p-3, "ess") ||
!BSTRCMPI(bp, p-14, "shape changers") ||
!BSTRCMPI(bp, p-15, "detect monsters") ||
#ifdef WIZARD
!BSTRCMPI(bp, p-9, "iron bars") ||
#endif
!BSTRCMPI(bp, p-5, "aklys"))
(p-4 == bp && !strcmpi(p-4, "lens"))) {
return bp;
}
mins:
*(p-1) = '\0'; /* drop s */
} else { /* input doesn't end in 's' */
/* inverse of some things that makeplural() can produce */
if (!BSTRCMPI(bp, p-5, "teeth")) {
Strcasecpy(p-4, "ooth");
return bp;
}
if (!BSTRCMPI(bp, p-4, "feet")) {
Strcasecpy(p-3, "oot");
return bp;
}
if (!BSTRCMPI(bp, p-3, "men")) {
Strcasecpy(p-2, "an");
return bp;
}
if (!BSTRCMPI(bp, p-4, "cubi") ||
!BSTRCMPI(bp, p-5, "fungi") ||
!BSTRCMPI(bp, p-9, "homunculi")) {
Strcasecpy(p-1, "us"); /* i -> us */
return bp;
}
if (!BSTRCMPI(bp, p-5, "djinn")) {
Strcasecpy(p, "i"); /* append i */
return bp;
}
if (!BSTRCMPI(bp, p-7, "mumakil")) {
*(p-2) = '\0'; /* drop il */
return bp;
}
if (p-4 >= bp && index("ml", lowc(*(p-4))) &&
!BSTRCMPI(bp, p-3, "ice")) {
Strcasecpy(p-3, "ouse");
return bp;
}
if (!BSTRCMPI(bp, p-8, "children")) {
*(p-3) = '\0'; /* drop ren */
return bp;
}
/* matzot -> matzo, algae -> alga */
if (!BSTRCMPI(bp, p-6, "matzot") ||
!BSTRCMPI(bp, p-5, "algae") ||
(p-6 >= bp && (!strcmpi(p-6, "hyphae") ||
!strcmpi(p-6, "larvae")))) {
!BSTRCMPI(bp, p-2, "ae")) {
*(p-1) = '\0'; /* drop t/e */
return bp;
}
@@ -2153,6 +2090,40 @@ boolean retry_inverted; /* optional extra "of" handling */
return FALSE;
}
struct o_range {
const char *name, oclass;
int f_o_range, l_o_range;
};
/* wishable subranges of objects */
STATIC_OVL NEARDATA const struct o_range o_ranges[] = {
{ "bag", TOOL_CLASS, SACK, BAG_OF_TRICKS },
{ "lamp", TOOL_CLASS, OIL_LAMP, MAGIC_LAMP },
{ "candle", TOOL_CLASS, TALLOW_CANDLE, WAX_CANDLE },
{ "horn", TOOL_CLASS, TOOLED_HORN, HORN_OF_PLENTY },
{ "shield", ARMOR_CLASS, SMALL_SHIELD, SHIELD_OF_REFLECTION },
{ "hat", ARMOR_CLASS, FEDORA, DUNCE_CAP },
{ "helm", ARMOR_CLASS, ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY },
{ "gloves", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY },
{ "gauntlets", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY },
{ "boots", ARMOR_CLASS, LOW_BOOTS, LEVITATION_BOOTS },
{ "shoes", ARMOR_CLASS, LOW_BOOTS, IRON_SHOES },
{ "cloak", ARMOR_CLASS, MUMMY_WRAPPING, CLOAK_OF_DISPLACEMENT },
#ifdef TOURIST
{ "shirt", ARMOR_CLASS, HAWAIIAN_SHIRT, T_SHIRT },
#endif
{ "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 },
#ifdef WIZARD
{ "venom", VENOM_CLASS, BLINDING_VENOM, ACID_VENOM },
#endif
{ "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;