diff --git a/include/extern.h b/include/extern.h index fe81c19d9..cef64a9d8 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1335,6 +1335,7 @@ E int FDECL(max_passive_dmg, (struct monst *,struct monst *)); E boolean FDECL(same_race, (struct permonst *,struct permonst *)); E int FDECL(monsndx, (struct permonst *)); E int FDECL(name_to_mon, (const char *)); +E int FDECL(name_to_monclass, (const char *,int *)); E int FDECL(gender, (struct monst *)); E int FDECL(pronoun_gender, (struct monst *)); E boolean FDECL(levl_follower, (struct monst *)); diff --git a/include/monsym.h b/include/monsym.h index 6c7f11c9f..ea18d8f9f 100644 --- a/include/monsym.h +++ b/include/monsym.h @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)monsym.h 3.5 1992/10/18 */ +/* SCCS Id: @(#)monsym.h 3.5 2007/04/07 */ /* Monster symbols and creation information rev 1.0 */ /* NetHack may be freely redistributed. See license for details. */ @@ -44,6 +44,7 @@ #define S_FUNGUS 32 #define S_GNOME 33 #define S_GIANT 34 +#define S_invisible 35 /* non-class present in def_monsyms[] */ #define S_JABBERWOCK 36 #define S_KOP 37 #define S_LICH 38 @@ -73,11 +74,6 @@ #define MAXMCLASSES 61 /* number of monster classes */ -#if 0 /* moved to decl.h so that makedefs.c won't see them */ -extern const char def_monsyms[MAXMCLASSES]; /* default class symbols */ -extern uchar monsyms[MAXMCLASSES]; /* current class symbols */ -#endif - /* * Default characters for monsters. These correspond to the monster classes * above. diff --git a/src/mondata.c b/src/mondata.c index 86981c519..3cd514041 100644 --- a/src/mondata.c +++ b/src/mondata.c @@ -518,6 +518,13 @@ monsndx(ptr) /* return an index into the mons array */ return(i); } +/* for handling alternate spellings */ +struct alt_spl { + const char *name; + short pm_val; +}; + +/* figure out what type of monster a user-supplied string is specifying */ int name_to_mon(in_str) const char *in_str; @@ -562,8 +569,7 @@ const char *in_str; slen = strlen(str); /* length possibly needs recomputing */ { - static const struct alt_spl { const char* name; short pm_val; } - names[] = { + static const struct alt_spl names[] = { /* Alternate spellings */ { "grey dragon", PM_GRAY_DRAGON }, { "baby grey dragon", PM_BABY_GRAY_DRAGON }, @@ -578,6 +584,8 @@ const char *in_str; to the rank title prefix (input has been singularized) */ { "master thief", PM_MASTER_OF_THIEVES }, { "master of assassin", PM_MASTER_ASSASSIN }, + /* Outdated names */ + { "invisible stalker", PM_STALKER }, /* Hyphenated names */ { "ki rin", PM_KI_RIN }, { "uruk hai", PM_URUK_HAI }, @@ -605,7 +613,7 @@ const char *in_str; { "mumakil", PM_MUMAK }, { "erinyes", PM_ERINYS }, /* end of list */ - { 0, 0 } + { 0, NON_PM } }; register const struct alt_spl *namep; @@ -637,6 +645,81 @@ const char *in_str; return mntmp; } +/* monster class from user input; used for genocide and controlled polymorph; + returns 0 rather than MAXMCLASSES if no match is found */ +int +name_to_monclass(in_str, mndx_p) +const char *in_str; +int *mndx_p; +{ + /* Single letters are matched against def_monsyms[].sym; words + or phrases are first matched against def_monsyms[].explain + to check class description; if not found there, then against + mons[].mname to test individual monster types. Input can be a + substring of the full description or mname, but to be accepted, + such partial matches must start at beginning of a word. Some + class descriptions include "foo or bar" and "foo or other foo" + so we don't want to accept "or", "other", "or other" there. */ + static NEARDATA const char * const falsematch[] = { + /* multiple-letter input which matches any of these gets rejected */ + "an", "the", "or", "other", "or other", 0 + }; + static NEARDATA const struct alt_spl truematch[] = { + /* "long worm" won't match "worm" class but would accidentally match + "long worm tail" class before the comparison with monster types */ + { "long worm", PM_LONG_WORM }, + /* some plausible guesses which need help */ + { "bug", -S_XAN }, /* would match bugbear... */ + { "fish", -S_EEL }, /* wouldn't match anything */ + /* end of list */ + { 0, NON_PM } + }; + const char *p, *x; + int i; + + if (mndx_p) *mndx_p = NON_PM; /* haven't [yet] matched a specific type */ + + if (!in_str || !in_str[0]) { + /* empty input */ + return 0; + } else if (!in_str[1]) { + /* single character */ + i = def_char_to_monclass(*in_str); + if (i == MAXMCLASSES) + i = (*in_str == DEF_INVISIBLE) ? S_invisible : 0; + return i; + } else { + /* multiple characters */ + in_str = makesingular(in_str); + /* check for special cases */ + for (i = 0; falsematch[i]; i++) + if (!strcmpi(in_str, falsematch[i])) return 0; + for (i = 0; truematch[i].name; i++) + if (!strcmpi(in_str, truematch[i].name)) { + i = truematch[i].pm_val; + if (i < 0) return -i; /* class */ + if (mndx_p) *mndx_p = i; /* monster */ + return mons[i].mlet; + } + /* check monster class descriptions */ + for (i = 1; i < MAXMCLASSES; i++) { + x = def_monsyms[i].explain; + if ((p = strstri(x, in_str)) != 0 && (p == x || *(p - 1) == ' ')) + return i; + } + /* check individual species names; not as thorough as mon_to_name() + but our caller can call that directly if desired */ + for (i = LOW_PM; i < NUMMONS; i++) { + x = mons[i].mname; + if ((p = strstri(x, in_str)) != 0 && (p == x || *(p - 1) == ' ')) { + if (mndx_p) *mndx_p = i; + return mons[i].mlet; + } + } + } + return 0; +} + /* returns 3 values (0=male, 1=female, 2=none) */ int gender(mtmp) diff --git a/src/read.c b/src/read.c index 25b86ea51..fcf5c3b91 100644 --- a/src/read.c +++ b/src/read.c @@ -1,4 +1,4 @@ -/* SCCS Id: @(#)read.c 3.5 2006/06/13 */ +/* SCCS Id: @(#)read.c 3.5 2007/04/07 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1605,61 +1605,28 @@ do_class_genocide() getlin("What class of monsters do you wish to genocide?", buf); (void)mungspaces(buf); - } while (buf[0]=='\033' || !buf[0]); + if (*buf == '\033') Strcpy(buf, "none"); /* hangup? */ + } while (!*buf); /* choosing "none" preserves genocideless conduct */ if (!strcmpi(buf, "none") || !strcmpi(buf, "nothing")) return; - if (strlen(buf) == 1) { - if (buf[0] == ILLOBJ_SYM) - buf[0] = def_monsyms[S_MIMIC].sym; - class = def_char_to_monclass(buf[0]); - } else { - char buf2[BUFSZ]; - - class = 0; - Strcpy(buf2, makesingular(buf)); - Strcpy(buf, buf2); - } + class = name_to_monclass(buf, (int *)0); + if (class == 0 && (i = name_to_mon(buf)) != NON_PM) + class = mons[i].mlet; immunecnt = gonecnt = goodcnt = 0; for (i = LOW_PM; i < NUMMONS; i++) { - if (class == 0 && - strstri(def_monsyms[(int)mons[i].mlet].explain, buf) != 0) - class = mons[i].mlet; if (mons[i].mlet == class) { if (!(mons[i].geno & G_GENO)) immunecnt++; else if(mvitals[i].mvflags & G_GENOD) gonecnt++; else goodcnt++; } } - /* - * If user's input doesn't match any class - * description, check individual species names. - */ - if (class == 0) { - for (i = LOW_PM; i < NUMMONS; i++) { - if (strstri(mons[i].mname, buf) != 0) { - class = mons[i].mlet; - break; - } - } - - if (class != 0) { - for (i = LOW_PM; i < NUMMONS; i++) { - if (mons[i].mlet == class) { - if (!(mons[i].geno & G_GENO)) immunecnt++; - else if(mvitals[i].mvflags & G_GENOD) gonecnt++; - else goodcnt++; - } - } - } - } if (!goodcnt && class != mons[urole.malenum].mlet && class != mons[urace.malenum].mlet) { if (gonecnt) pline("All such monsters are already nonexistent."); - else if (immunecnt || - (buf[0] == DEF_INVISIBLE && buf[1] == '\0')) + else if (immunecnt || class == S_invisible) You("aren't permitted to genocide such monsters."); else #ifdef WIZARD /* to aid in topology testing; remove pesky monsters */