]> granicus.if.org Git - nethack/commitdiff
tty role selection when filter by options
authorPatR <rankin@nethack.org>
Tue, 2 Jun 2015 01:18:47 +0000 (18:18 -0700)
committerPatR <rankin@nethack.org>
Tue, 2 Jun 2015 01:18:47 +0000 (18:18 -0700)
Honor things like OPTIONS:role=!tourist and NETHACKOPTIONS='race=!orc'
when performing interactive role selection.  I don't think it was
completely correct when players let the program choose, but it must
have been close enough because we haven't gotten any complaints.
The post-3.4.3 interactive selection was ignoring options-base filtering
entirely and did get complaints for the pre-beta.

Role selection has a ton of code which bloats the program without doing
anything useful for actual game play.  It ought to be split off into a
separate front end.

include/extern.h
include/winprocs.h
include/wintype.h
src/role.c
win/tty/wintty.c

index a4f355a09cacb11bb1ace2a80db2c3763d665cc5..67988dcbde34e6150dfb65445bdd9fcf644ecf1a 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6 extern.h        $NHDT-Date: 1433050874 2015/05/31 05:41:14 $  $NHDT-Branch: master $:$NHDT-Revision: 1.499 $ */
+/* NetHack 3.6 extern.h        $NHDT-Date: 1433207912 2015/06/02 01:18:32 $  $NHDT-Branch: master $:$NHDT-Revision: 1.500 $ */
 /* Copyright (c) Steve Creps, 1988.                              */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -1982,8 +1982,8 @@ E int FDECL(randgend, (int, int));
 E int FDECL(randalign, (int, int));
 E int FDECL(str2role, (const char *));
 E int FDECL(str2race, (const char *));
-E int FDECL(str2gend, (char *));
-E int FDECL(str2align, (char *));
+E int FDECL(str2gend, (const char *));
+E int FDECL(str2align, (const char *));
 E boolean FDECL(ok_role, (int, int, int, int));
 E int FDECL(pick_role, (int, int, int, int));
 E boolean FDECL(ok_race, (int, int, int, int));
@@ -1993,7 +1993,9 @@ E int FDECL(pick_gend, (int, int, int, int));
 E boolean FDECL(ok_align, (int, int, int, int));
 E int FDECL(pick_align, (int, int, int, int));
 E void NDECL(rigid_role_checks);
-E boolean FDECL(setrolefilter, (char *));
+E boolean FDECL(setrolefilter, (const char *));
+E boolean NDECL(gotrolefilter);
+E void NDECL(clearrolefilter);
 E char *FDECL(build_plselection_prompt, (char *, int, int, int, int, int));
 E char *FDECL(root_plselection_prompt, (char *, int, int, int, int, int));
 E void NDECL(plnamesuffix);
index 149285e9b26c2c436402aa370194d60d83c6f477..47b71c9ad3dafd7b68210242398bd67aac9415ab 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6 winprocs.h      $NHDT-Date: 1432512776 2015/05/25 00:12:56 $  $NHDT-Branch: master $:$NHDT-Revision: 1.34 $ */
+/* NetHack 3.6 winprocs.h      $NHDT-Date: 1433207914 2015/06/02 01:18:34 $  $NHDT-Branch: master $:$NHDT-Revision: 1.35 $ */
 /* Copyright (c) David Cohrs, 1992                               */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -289,7 +289,8 @@ struct wc_Opt {
 #define RS_RACE 2
 #define RS_GENDER 3
 #define RS_ALGNMNT 4
-#define RS_menu_arg(x) (ROLE_RANDOM - ((x) + 1)) /* 0..4 -> -3..-7 */
+#define RS_filter 5
+#define RS_menu_arg(x) (ROLE_RANDOM - ((x) + 1)) /* 0..5 -> -3..-8 */
 
 /* Choose_windows() may be called multiple times; these constants tell the
  * init function whether the window system is coming or going. */
index fdd4ca814343691a11deeda37d488a9f1238d13b..960f652746cd645094f08011a5803d4448803478 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6  wintype.h       $NHDT-Date: 1432512782 2015/05/25 00:13:02 $  $NHDT-Branch: master $:$NHDT-Revision: 1.14 $ */
+/* NetHack 3.6  wintype.h       $NHDT-Date: 1433207914 2015/06/02 01:18:34 $  $NHDT-Branch: master $:$NHDT-Revision: 1.15 $ */
 /* Copyright (c) David Cohrs, 1991                                */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -22,6 +22,7 @@ typedef union any {
     long *a_lptr;
     unsigned long *a_ulptr;
     unsigned *a_uptr;
+    const char *a_string;
     /* add types as needed */
 } anything;
 #define ANY_P union any /* avoid typedef in prototypes */
index 117a5f708165fcc6004bd480e0287754611dd3bc..d593faabc20781ea205e30d655b0b9c59a860ce1 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6 role.c  $NHDT-Date: 1432512766 2015/05/25 00:12:46 $  $NHDT-Branch: master $:$NHDT-Revision: 1.30 $ */
+/* NetHack 3.6 role.c  $NHDT-Date: 1433207910 2015/06/02 01:18:30 $  $NHDT-Branch: master $:$NHDT-Revision: 1.31 $ */
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985-1999. */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -614,49 +614,30 @@ const struct Role roles[] = {
 /* The player's role, created at runtime from initial
  * choices.  This may be munged in role_init().
  */
-struct Role urole = { { "Undefined", 0 },
-                      { { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 },
-                        { 0, 0 } },
-                      "L",
-                      "N",
-                      "C",
-                      "Xxx",
-                      "home",
-                      "locate",
-                      NON_PM,
-                      NON_PM,
-                      NON_PM,
-                      NON_PM,
-                      NON_PM,
-                      NON_PM,
-                      NON_PM,
-                      NON_PM,
-                      0,
-                      0,
-                      0,
-                      0,
-                      /* Str Int Wis Dex Con Cha */
-                      { 7, 7, 7, 7, 7, 7 },
-                      { 20, 15, 15, 20, 20, 10 },
-                      /* Init   Lower  Higher */
-                      { 10, 0, 0, 8, 1, 0 }, /* Hit points */
-                      { 2, 0, 0, 2, 0, 3 },
-                      14, /* Energy */
-                      0,
-                      10,
-                      0,
-                      0,
-                      4,
-                      A_INT,
-                      0,
-                      -3 };
+struct Role urole = {
+    { "Undefined", 0 },
+    { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 },
+      { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+    "L", "N", "C",
+    "Xxx", "home", "locate",
+    NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM, NON_PM,
+    0, 0, 0, 0,
+    /* Str Int Wis Dex Con Cha */
+    { 7, 7, 7, 7, 7, 7 },
+    { 20, 15, 15, 20, 20, 10 },
+    /* Init   Lower  Higher */
+    { 10, 0, 0, 8, 1, 0 }, /* Hit points */
+    { 2, 0, 0, 2, 0, 3 },
+    14, /* Energy */
+     0,
+    10,
+     0,
+     0,
+     4,
+    A_INT,
+     0,
+    -3
+};
 
 /* Table of all races */
 const struct Race races[] = {
@@ -816,6 +797,7 @@ static struct {
     short mask;
 } filter;
 
+STATIC_DCL int NDECL(randrole_filtered);
 STATIC_DCL char *FDECL(promptsep, (char *, int));
 STATIC_DCL int FDECL(role_gendercount, (int));
 STATIC_DCL int FDECL(race_alignmentcount, (int));
@@ -836,6 +818,22 @@ randrole()
     return (rn2(SIZE(roles) - 1));
 }
 
+STATIC_OVL int
+randrole_filtered()
+{
+    int i, n = 0, set[SIZE(roles)];
+
+    /* this doesn't rule out impossible combinations but attempts to
+       honor all the filter masks */
+    for (i = 0; i < SIZE(roles); ++i)
+        if (ok_role(i, ROLE_NONE, ROLE_NONE, ROLE_NONE)
+            && ok_race(i, ROLE_RANDOM, ROLE_NONE, ROLE_NONE)
+            && ok_gend(i, ROLE_NONE, ROLE_RANDOM, ROLE_NONE)
+            && ok_align(i, ROLE_NONE, ROLE_NONE, ROLE_RANDOM))
+            set[n++] = i;
+    return n ? set[rn2(n)] : randrole();
+}
+
 int
 str2role(str)
 const char *str;
@@ -973,7 +971,7 @@ int rolenum, racenum;
 
 int
 str2gend(str)
-char *str;
+const char *str;
 {
     int i, len;
 
@@ -1039,7 +1037,7 @@ int rolenum, racenum;
 
 int
 str2align(str)
-char *str;
+const char *str;
 {
     int i, len;
 
@@ -1074,6 +1072,8 @@ int rolenum, racenum, gendnum, alignnum;
     short allow;
 
     if (rolenum >= 0 && rolenum < SIZE(roles) - 1) {
+        if (filter.roles[rolenum])
+            return FALSE;
         allow = roles[rolenum].allow;
         if (racenum >= 0 && racenum < SIZE(races) - 1
             && !(allow & races[racenum].allow & ROLE_RACEMASK))
@@ -1084,11 +1084,12 @@ int rolenum, racenum, gendnum, alignnum;
         if (alignnum >= 0 && alignnum < ROLE_ALIGNS
             && !(allow & aligns[alignnum].allow & ROLE_ALIGNMASK))
             return FALSE;
-        if (filter.roles[rolenum])
-            return FALSE;
         return TRUE;
     } else {
+        /* random; check whether any selection is possible */
         for (i = 0; i < SIZE(roles) - 1; i++) {
+            if (filter.roles[i])
+                continue;
             allow = roles[i].allow;
             if (racenum >= 0 && racenum < SIZE(races) - 1
                 && !(allow & races[racenum].allow & ROLE_RACEMASK))
@@ -1113,24 +1114,21 @@ pick_role(racenum, gendnum, alignnum, pickhow)
 int racenum, gendnum, alignnum, pickhow;
 {
     int i;
-    int roles_ok = 0;
+    int roles_ok = 0, set[SIZE(roles)];
 
     for (i = 0; i < SIZE(roles) - 1; i++) {
-        if (ok_role(i, racenum, gendnum, alignnum))
-            roles_ok++;
+        if (ok_role(i, racenum, gendnum, alignnum)
+            && ok_race(i, (racenum >= 0) ? racenum : ROLE_RANDOM,
+                       gendnum, alignnum)
+            && ok_gend(i, racenum,
+                       (gendnum >= 0) ? gendnum : ROLE_RANDOM, alignnum)
+            && ok_race(i, racenum,
+                       gendnum, (alignnum >= 0) ? alignnum : ROLE_RANDOM))
+            set[roles_ok++] = i;
     }
     if (roles_ok == 0 || (roles_ok > 1 && pickhow == PICK_RIGID))
         return ROLE_NONE;
-    roles_ok = rn2(roles_ok);
-    for (i = 0; i < SIZE(roles) - 1; i++) {
-        if (ok_role(i, racenum, gendnum, alignnum)) {
-            if (roles_ok == 0)
-                return i;
-            else
-                roles_ok--;
-        }
-    }
-    return ROLE_NONE;
+    return set[rn2(roles_ok)];
 }
 
 /* is racenum compatible with any rolenum/gendnum/alignnum constraints? */
@@ -1142,6 +1140,8 @@ int rolenum, racenum, gendnum, alignnum;
     short allow;
 
     if (racenum >= 0 && racenum < SIZE(races) - 1) {
+        if (filter.mask & races[racenum].selfmask)
+            return FALSE;
         allow = races[racenum].allow;
         if (rolenum >= 0 && rolenum < SIZE(roles) - 1
             && !(allow & roles[rolenum].allow & ROLE_RACEMASK))
@@ -1152,11 +1152,12 @@ int rolenum, racenum, gendnum, alignnum;
         if (alignnum >= 0 && alignnum < ROLE_ALIGNS
             && !(allow & aligns[alignnum].allow & ROLE_ALIGNMASK))
             return FALSE;
-        if (filter.mask & races[racenum].selfmask)
-            return FALSE;
         return TRUE;
     } else {
+        /* random; check whether any selection is possible */
         for (i = 0; i < SIZE(races) - 1; i++) {
+            if (filter.mask & races[i].selfmask)
+                continue;
             allow = races[i].allow;
             if (rolenum >= 0 && rolenum < SIZE(roles) - 1
                 && !(allow & roles[rolenum].allow & ROLE_RACEMASK))
@@ -1212,6 +1213,8 @@ int alignnum UNUSED;
     short allow;
 
     if (gendnum >= 0 && gendnum < ROLE_GENDERS) {
+        if (filter.mask & genders[gendnum].allow)
+            return FALSE;
         allow = genders[gendnum].allow;
         if (rolenum >= 0 && rolenum < SIZE(roles) - 1
             && !(allow & roles[rolenum].allow & ROLE_GENDMASK))
@@ -1219,11 +1222,12 @@ int alignnum UNUSED;
         if (racenum >= 0 && racenum < SIZE(races) - 1
             && !(allow & races[racenum].allow & ROLE_GENDMASK))
             return FALSE;
-        if (filter.mask & genders[gendnum].allow)
-            return FALSE;
         return TRUE;
     } else {
+        /* random; check whether any selection is possible */
         for (i = 0; i < ROLE_GENDERS; i++) {
+            if (filter.mask & genders[i].allow)
+                continue;
             allow = genders[i].allow;
             if (rolenum >= 0 && rolenum < SIZE(roles) - 1
                 && !(allow & roles[rolenum].allow & ROLE_GENDMASK))
@@ -1278,6 +1282,8 @@ int alignnum;
     short allow;
 
     if (alignnum >= 0 && alignnum < ROLE_ALIGNS) {
+        if (filter.mask & aligns[alignnum].allow)
+            return FALSE;
         allow = aligns[alignnum].allow;
         if (rolenum >= 0 && rolenum < SIZE(roles) - 1
             && !(allow & roles[rolenum].allow & ROLE_ALIGNMASK))
@@ -1285,11 +1291,12 @@ int alignnum;
         if (racenum >= 0 && racenum < SIZE(races) - 1
             && !(allow & races[racenum].allow & ROLE_ALIGNMASK))
             return FALSE;
-        if (filter.mask & aligns[alignnum].allow)
-            return FALSE;
         return TRUE;
     } else {
+        /* random; check whether any selection is possible */
         for (i = 0; i < ROLE_ALIGNS; i++) {
+            if (filter.mask & aligns[i].allow)
+                return FALSE;
             allow = aligns[i].allow;
             if (rolenum >= 0 && rolenum < SIZE(roles) - 1
                 && !(allow & roles[rolenum].allow & ROLE_ALIGNMASK))
@@ -1352,7 +1359,7 @@ rigid_role_checks()
         flags.initrole = pick_role(flags.initrace, flags.initgend,
                                    flags.initalign, PICK_RANDOM);
         if (flags.initrole < 0)
-            flags.initrole = randrole();
+            flags.initrole = randrole_filtered();
     }
     if (flags.initrole != ROLE_NONE) {
         if (flags.initrace == ROLE_NONE)
@@ -1369,7 +1376,7 @@ rigid_role_checks()
 
 boolean
 setrolefilter(bufp)
-char *bufp;
+const char *bufp;
 {
     int i;
     boolean reslt = TRUE;
@@ -1387,6 +1394,29 @@ char *bufp;
     return reslt;
 }
 
+boolean
+gotrolefilter()
+{
+    int i;
+
+    if (filter.mask)
+        return TRUE;
+    for (i = 0; i < SIZE(roles); ++i)
+        if (filter.roles[i])
+            return TRUE;
+    return FALSE;
+}
+
+void
+clearrolefilter()
+{
+    int i;
+
+    for (i = 0; i < SIZE(roles); ++i)
+        filter.roles[i] = FALSE;
+    filter.mask = 0;
+}
+
 #define BP_ALIGN 0
 #define BP_GEND 1
 #define BP_RACE 2
@@ -1401,6 +1431,7 @@ char *buf;
 int num_post_attribs;
 {
     const char *conjuct = "and ";
+
     if (num_post_attribs > 1 && post_attribs < num_post_attribs
         && post_attribs > 1)
         Strcat(buf, ",");
@@ -1416,6 +1447,7 @@ role_gendercount(rolenum)
 int rolenum;
 {
     int gendcount = 0;
+
     if (validrole(rolenum)) {
         if (roles[rolenum].allow & ROLE_MALE)
             ++gendcount;
@@ -1432,6 +1464,7 @@ race_alignmentcount(racenum)
 int racenum;
 {
     int aligncount = 0;
+
     if (racenum != ROLE_NONE && racenum != ROLE_RANDOM) {
         if (races[racenum].allow & ROLE_CHAOTIC)
             ++aligncount;
@@ -1487,8 +1520,8 @@ int buflen, rolenum, racenum, gendnum, alignnum;
         if (alignnum != ROLE_RANDOM)
             alignnum = ROLE_NONE;
         /* if alignment not specified, but race is specified
-                and only one choice of alignment for that race then
-                don't include it in the later list */
+           and only one choice of alignment for that race then
+           don't include it in the later list */
         if ((((racenum != ROLE_NONE && racenum != ROLE_RANDOM)
               && ok_race(rolenum, racenum, gendnum, alignnum))
              && (aligncount > 1))
@@ -1619,13 +1652,12 @@ int buflen, rolenum, racenum, gendnum, alignnum;
     Sprintf(buf, "%s", s_suffix(tmpbuf));
 
     /* buf should now be:
-     * < your lawful female gnomish cavewoman's> || <your lawful female
-     * gnome's>
-     *    || <your lawful female character's>
+     *    <your lawful female gnomish cavewoman's>
+     * || <your lawful female gnome's>
+     * || <your lawful female character's>
      *
      * Now append the post attributes to it
      */
-
     num_post_attribs = post_attribs;
     if (post_attribs) {
         if (pa[BP_RACE]) {
@@ -1811,7 +1843,7 @@ winid where;
     anything any;
     char buf[BUFSZ];
     const char *what = 0, *constrainer = 0, *forcedvalue = 0;
-    int f = 0, r, c, g, a, allowmask;
+    int f = 0, r, c, g, a, i, allowmask;
 
     r = flags.initrole;
     c = flags.initrace;
@@ -1822,7 +1854,13 @@ winid where;
     case RS_ROLE:
         what = "role";
         f = r;
-        /* nothing contrains role to a single choice */
+        for (i = 0; i < SIZE(roles); ++i)
+            if (i != f && !filter.roles[i])
+                break;
+        if (i == SIZE(roles)) {
+            constrainer = "filter";
+            forcedvalue = "role";
+        }
         break;
     case RS_RACE:
         what = "race";
@@ -1835,6 +1873,12 @@ winid where;
             if (c >= 0) {
                 constrainer = "role";
                 forcedvalue = races[c].noun;
+            } else if (f >= 0
+                       && (allowmask & ~filter.mask) == races[f].selfmask) {
+                /* if there is only one race choice available due to user
+                   options disallowing others, race menu entry is disabled */
+                constrainer = "filter";
+                forcedvalue = "race";
             }
         }
         break;
@@ -1851,6 +1895,12 @@ winid where;
             if (g >= 0) {
                 constrainer = "role";
                 forcedvalue = genders[g].adj;
+            } else if (f >= 0
+                       && (allowmask & ~filter.mask) == genders[f].allow) {
+                /* if there is only one gender choice available due to user
+                   options disallowing other, gender menu entry is disabled */
+                constrainer = "filter";
+                forcedvalue = "gender";
             }
         }
         break;
@@ -1880,6 +1930,13 @@ winid where;
             if (a >= 0)
                 constrainer = "race";
         }
+        if (f >= 0 && !constrainer
+            && (ROLE_ALIGNMASK & ~filter.mask) == aligns[f].allow) {
+            /* if there is only one alignment choice available due to user
+               options disallowing others, algn menu entry is disabled */
+            constrainer = "filter";
+            forcedvalue = "alignment";
+        }
         if (a >= 0)
             forcedvalue = aligns[a].adj;
         break;
@@ -1897,6 +1954,10 @@ winid where;
         Sprintf(buf, "Pick%s %s first", (f >= 0) ? " another" : "", what);
         add_menu(where, NO_GLYPH, &any, RS_menu_let[which], 0, ATR_NONE, buf,
                  MENU_UNSELECTED);
+    } else if (which == RS_filter) {
+        any.a_int = RS_menu_arg(RS_filter);
+        add_menu(where, NO_GLYPH, &any, '~', 0, ATR_NONE,
+                 "Reset role/race/&c filtering", MENU_UNSELECTED);
     } else if (which == ROLE_RANDOM) {
         any.a_int = ROLE_RANDOM;
         add_menu(where, NO_GLYPH, &any, '*', 0, ATR_NONE, "Random",
@@ -1942,7 +2003,7 @@ role_init()
         /* Try the player letter second */
         if ((flags.initrole = str2role(pl_character)) < 0)
             /* None specified; pick a random role */
-            flags.initrole = randrole();
+            flags.initrole = randrole_filtered();
     }
 
     /* We now have a valid role index.  Copy the role name back. */
index e3cebdfb92cb0cd03cb17ac24466c176d92eb82a..9a24a424dc344c79f54267a01adcd3e69ae242b1 100644 (file)
@@ -1,4 +1,4 @@
-/* NetHack 3.6 wintty.c        $NHDT-Date: 1432536533 2015/05/25 06:48:53 $  $NHDT-Branch: master $:$NHDT-Revision: 1.94 $ */
+/* NetHack 3.6 wintty.c        $NHDT-Date: 1433207911 2015/06/02 01:18:31 $  $NHDT-Branch: master $:$NHDT-Revision: 1.95 $ */
 /* Copyright (c) David Cohrs, 1991                               */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -158,6 +158,11 @@ STATIC_DCL const char *FDECL(compress_str, (const char *));
 STATIC_DCL void FDECL(tty_putsym, (winid, int, int, CHAR_P));
 STATIC_DCL char *FDECL(copy_of, (const char *));
 STATIC_DCL void FDECL(bail, (const char *)); /* __attribute__((noreturn)) */
+STATIC_DCL void FDECL(setup_rolemenu, (winid, BOOLEAN_P, int, int, int));
+STATIC_DCL void FDECL(setup_racemenu, (winid, BOOLEAN_P, int, int, int));
+STATIC_DCL void FDECL(setup_gendmenu, (winid, BOOLEAN_P, int, int, int));
+STATIC_DCL void FDECL(setup_algnmenu, (winid, BOOLEAN_P, int, int, int));
+STATIC_DCL boolean NDECL(reset_role_filtering);
 
 /*
  * A string containing all the default commands -- to add to a list
@@ -327,7 +332,7 @@ tty_player_selection()
 {
     int i, k, n, choice, nextpick;
     boolean getconfirmation;
-    char pick4u = 'n', thisch, lastch = 0;
+    char pick4u = 'n';
     char pbuf[QBUFSZ], plbuf[QBUFSZ];
     winid win;
     anything any;
@@ -351,8 +356,8 @@ tty_player_selection()
     if (ROLE == ROLE_NONE || RACE == ROLE_NONE || GEND == ROLE_NONE
         || ALGN == ROLE_NONE) {
         int echoline;
-        char *prompt =
-            build_plselection_prompt(pbuf, QBUFSZ, ROLE, RACE, GEND, ALGN);
+        char *prompt = build_plselection_prompt(pbuf, QBUFSZ,
+                                                ROLE, RACE, GEND, ALGN);
 
         /* this prompt string ends in "[ynaq]?":
            y - game picks role,&c then asks player to confirm;
@@ -404,36 +409,13 @@ makepicks:
                     }
                 } else {
                     /* Prompt for a role */
-                    char rolenamebuf[QBUFSZ];
-
                     tty_clear_nhwindow(BASE_WINDOW);
                     role_selection_prolog(RS_ROLE, BASE_WINDOW);
                     win = create_nhwindow(NHW_MENU);
                     start_menu(win);
-                    any = zeroany; /* zero out all bits */
-                    for (i = 0; roles[i].name.m; i++) {
-                        if (!ok_role(i, RACE, GEND, ALGN))
-                            continue;
-                        any.a_int = i + 1; /* must be non-zero */
-                        thisch = lowc(roles[i].name.m[0]);
-                        if (thisch == lastch)
-                            thisch = highc(thisch);
-                        Strcpy(rolenamebuf, roles[i].name.m);
-                        if (roles[i].name.f) {
-                            /* role has distinct name for female (C,P) */
-                            if (GEND == 1) {
-                                /* female already chosen; replace male name */
-                                Strcpy(rolenamebuf, roles[i].name.f);
-                            } else if (GEND < 0) {
-                                /* not chosen yet; append slash+female name */
-                                Strcat(rolenamebuf, "/");
-                                Strcat(rolenamebuf, roles[i].name.f);
-                            }
-                        }
-                        add_menu(win, NO_GLYPH, &any, thisch, 0, ATR_NONE,
-                                 an(rolenamebuf), MENU_UNSELECTED);
-                        lastch = thisch;
-                    }
+                    /* populate the menu with role choices */
+                    setup_rolemenu(win, TRUE, RACE, GEND, ALGN);
+                    /* add miscellaneous menu entries */
                     role_menu_extra(ROLE_RANDOM, win);
                     any.a_int = 0; /* separator, not a choice */
                     add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE, "",
@@ -441,6 +423,8 @@ makepicks:
                     role_menu_extra(RS_RACE, win);
                     role_menu_extra(RS_GENDER, win);
                     role_menu_extra(RS_ALGNMNT, win);
+                    if (gotrolefilter())
+                        role_menu_extra(RS_filter, win);
                     role_menu_extra(ROLE_NONE, win); /* quit */
                     Strcpy(pbuf, "Pick a role or profession");
                     end_menu(win, pbuf);
@@ -461,6 +445,10 @@ makepicks:
                     } else if (choice == RS_menu_arg(RS_RACE)) {
                         RACE = k = ROLE_NONE;
                         nextpick = RS_RACE;
+                    } else if (choice == RS_menu_arg(RS_filter)) {
+                        ROLE = k = ROLE_NONE;
+                        (void) reset_role_filtering();
+                        nextpick = RS_ROLE;
                     } else if (choice == ROLE_RANDOM) {
                         k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
                         if (k < 0)
@@ -509,14 +497,9 @@ makepicks:
                         win = create_nhwindow(NHW_MENU);
                         start_menu(win);
                         any = zeroany; /* zero out all bits */
-                        for (i = 0; races[i].noun; i++) {
-                            if (!ok_race(ROLE, i, GEND, ALGN))
-                                continue;
-                            any.a_int = i + 1; /* must be non-zero */
-                            add_menu(win, NO_GLYPH, &any, races[i].noun[0], 0,
-                                     ATR_NONE, races[i].noun,
-                                     MENU_UNSELECTED);
-                        }
+                        /* populate the menu with role choices */
+                        setup_racemenu(win, TRUE, ROLE, GEND, ALGN);
+                        /* add miscellaneous menu entries */
                         role_menu_extra(ROLE_RANDOM, win);
                         any.a_int = 0; /* separator, not a choice */
                         add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE, "",
@@ -524,12 +507,14 @@ makepicks:
                         role_menu_extra(RS_ROLE, win);
                         role_menu_extra(RS_GENDER, win);
                         role_menu_extra(RS_ALGNMNT, win);
+                        if (gotrolefilter())
+                            role_menu_extra(RS_filter, win);
                         role_menu_extra(ROLE_NONE, win); /* quit */
                         Strcpy(pbuf, "Pick a race or species");
                         end_menu(win, pbuf);
                         n = select_menu(win, PICK_ONE, &selected);
-                        choice =
-                            (n == 1) ? selected[0].item.a_int : ROLE_NONE;
+                        choice = (n == 1) ? selected[0].item.a_int
+                                          : ROLE_NONE;
                         if (selected)
                             free((genericptr_t) selected), selected = 0;
                         destroy_nhwindow(win);
@@ -545,6 +530,12 @@ makepicks:
                         } else if (choice == RS_menu_arg(RS_ROLE)) {
                             ROLE = k = ROLE_NONE;
                             nextpick = RS_ROLE;
+                        } else if (choice == RS_menu_arg(RS_filter)) {
+                            RACE = k = ROLE_NONE;
+                            if (reset_role_filtering())
+                                nextpick = RS_ROLE;
+                            else
+                                nextpick = RS_RACE;
                         } else if (choice == ROLE_RANDOM) {
                             k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
                             if (k < 0)
@@ -559,8 +550,8 @@ makepicks:
         }     /* picking race */
 
         if (nextpick == RS_GENDER) {
-            nextpick =
-                (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE : RS_ALGNMNT;
+            nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE
+                       : RS_ALGNMNT;
             /* Select a gender, if necessary;
                force compatibility with role/race, try for compatibility
                with pre-selected alignment. */
@@ -595,14 +586,9 @@ makepicks:
                         win = create_nhwindow(NHW_MENU);
                         start_menu(win);
                         any = zeroany; /* zero out all bits */
-                        for (i = 0; i < ROLE_GENDERS; i++) {
-                            if (!ok_gend(ROLE, RACE, i, ALGN))
-                                continue;
-                            any.a_int = i + 1; /* non-zero */
-                            add_menu(win, NO_GLYPH, &any, genders[i].adj[0],
-                                     0, ATR_NONE, genders[i].adj,
-                                     MENU_UNSELECTED);
-                        }
+                        /* populate the menu with gender choices */
+                        setup_gendmenu(win, TRUE, ROLE, RACE, ALGN);
+                        /* add miscellaneous menu entries */
                         role_menu_extra(ROLE_RANDOM, win);
                         any.a_int = 0; /* separator, not a choice */
                         add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE, "",
@@ -610,12 +596,14 @@ makepicks:
                         role_menu_extra(RS_ROLE, win);
                         role_menu_extra(RS_RACE, win);
                         role_menu_extra(RS_ALGNMNT, win);
+                        if (gotrolefilter())
+                            role_menu_extra(RS_filter, win);
                         role_menu_extra(ROLE_NONE, win); /* quit */
                         Strcpy(pbuf, "Pick a gender or sex");
                         end_menu(win, pbuf);
                         n = select_menu(win, PICK_ONE, &selected);
-                        choice =
-                            (n == 1) ? selected[0].item.a_int : ROLE_NONE;
+                        choice = (n == 1) ? selected[0].item.a_int
+                                          : ROLE_NONE;
                         if (selected)
                             free((genericptr_t) selected), selected = 0;
                         destroy_nhwindow(win);
@@ -631,6 +619,12 @@ makepicks:
                         } else if (choice == RS_menu_arg(RS_ROLE)) {
                             ROLE = k = ROLE_NONE;
                             nextpick = RS_ROLE;
+                        } else if (choice == RS_menu_arg(RS_filter)) {
+                            GEND = k = ROLE_NONE;
+                            if (reset_role_filtering())
+                                nextpick = RS_ROLE;
+                            else
+                                nextpick = RS_GENDER;
                         } else if (choice == ROLE_RANDOM) {
                             k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
                             if (k < 0)
@@ -645,8 +639,7 @@ makepicks:
         }     /* picking gender */
 
         if (nextpick == RS_ALGNMNT) {
-            nextpick =
-                (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE : RS_GENDER;
+            nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE : RS_GENDER;
             /* Select an alignment, if necessary;
                force compatibility with role/race/gender. */
             if (ALGN < 0 || !validalign(ROLE, RACE, ALGN)) {
@@ -680,14 +673,7 @@ makepicks:
                         win = create_nhwindow(NHW_MENU);
                         start_menu(win);
                         any = zeroany; /* zero out all bits */
-                        for (i = 0; i < ROLE_ALIGNS; i++) {
-                            if (!ok_align(ROLE, RACE, GEND, i))
-                                continue;
-                            any.a_int = i + 1; /* non-zero */
-                            add_menu(win, NO_GLYPH, &any, aligns[i].adj[0], 0,
-                                     ATR_NONE, aligns[i].adj,
-                                     MENU_UNSELECTED);
-                        }
+                        setup_algnmenu(win, TRUE, ROLE, RACE, GEND);
                         role_menu_extra(ROLE_RANDOM, win);
                         any.a_int = 0; /* separator, not a choice */
                         add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE, "",
@@ -695,12 +681,14 @@ makepicks:
                         role_menu_extra(RS_ROLE, win);
                         role_menu_extra(RS_RACE, win);
                         role_menu_extra(RS_GENDER, win);
+                        if (gotrolefilter())
+                            role_menu_extra(RS_filter, win);
                         role_menu_extra(ROLE_NONE, win); /* quit */
                         Strcpy(pbuf, "Pick an alignment or creed");
                         end_menu(win, pbuf);
                         n = select_menu(win, PICK_ONE, &selected);
-                        choice =
-                            (n == 1) ? selected[0].item.a_int : ROLE_NONE;
+                        choice = (n == 1) ? selected[0].item.a_int
+                                          : ROLE_NONE;
                         if (selected)
                             free((genericptr_t) selected), selected = 0;
                         destroy_nhwindow(win);
@@ -716,6 +704,12 @@ makepicks:
                         } else if (choice == RS_menu_arg(RS_ROLE)) {
                             ROLE = k = ROLE_NONE;
                             nextpick = RS_ROLE;
+                        } else if (choice == RS_menu_arg(RS_filter)) {
+                            ALGN = k = ROLE_NONE;
+                            if (reset_role_filtering())
+                                nextpick = RS_ROLE;
+                            else
+                                nextpick = RS_ALGNMNT;
                         } else if (choice == ROLE_RANDOM) {
                             k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
                             if (k < 0)
@@ -811,11 +805,11 @@ makepicks:
                 /* plnamesuffix() can change any or all of ROLE, RACE,
                    GEND, ALGN; we'll override that and honor only the name */
                 saveROLE = ROLE, saveRACE = RACE, saveGEND = GEND,
-                saveALGN = ALGN;
+                    saveALGN = ALGN;
                 *plname = '\0';
                 plnamesuffix(); /* calls askname() when plname[] is empty */
                 ROLE = saveROLE, RACE = saveRACE, GEND = saveGEND,
-                ALGN = saveALGN;
+                    ALGN = saveALGN;
             }
             break; /* getconfirmation is still True */
         case 2:    /* 'n' */
@@ -846,11 +840,198 @@ give_up:
     return;
 }
 
+STATIC_OVL boolean
+reset_role_filtering()
+{
+    winid win;
+    anything any;
+    int i, n;
+    menu_item *selected = 0;
+
+    win = create_nhwindow(NHW_MENU);
+    start_menu(win);
+    any = zeroany;
+
+    /* no extra blank line preceding this entry; end_menu supplies one */
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
+             "Unacceptable roles", MENU_UNSELECTED);
+    setup_rolemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
+
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
+             "Unacceptable races", MENU_UNSELECTED);
+    setup_racemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
+
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
+             "Unacceptable genders", MENU_UNSELECTED);
+    setup_gendmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
+
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, "", MENU_UNSELECTED);
+    add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE,
+             "Uncceptable alignments", MENU_UNSELECTED);
+    setup_algnmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
+
+    end_menu(win, "Pick all that apply");
+    n = select_menu(win, PICK_ANY, &selected);
+
+    if (n > 0) {
+        clearrolefilter();
+        for (i = 0; i < n; i++)
+            setrolefilter(selected[i].item.a_string);
+
+        ROLE = RACE = GEND = ALGN = ROLE_NONE;
+    }
+    if (selected)
+        free((genericptr_t) selected), selected = 0;
+    destroy_nhwindow(win);
+    return (n > 0) ? TRUE : FALSE;
+}
+
 #undef ROLE
 #undef RACE
 #undef GEND
 #undef ALGN
 
+/* add entries a-Archeologist, b-Barbarian, &c to menu being built in 'win' */
+STATIC_OVL void
+setup_rolemenu(win, filtering, race, gend, algn)
+winid win;
+boolean filtering; /* True => exclude filtered roles; False => filter reset */
+int race, gend, algn; /* all ROLE_NONE for !filtering case */
+{
+    anything any;
+    int i;
+    boolean role_ok;
+    char thisch, lastch = '\0', rolenamebuf[50];
+
+    any = zeroany; /* zero out all bits */
+    for (i = 0; roles[i].name.m; i++) {
+        role_ok = ok_role(i, race, gend, algn);
+        if (filtering && !role_ok)
+            continue;
+        if (filtering)
+            any.a_int = i + 1;
+        else
+            any.a_string = roles[i].name.m;
+        thisch = lowc(*roles[i].name.m);
+        if (thisch == lastch)
+            thisch = highc(thisch);
+        Strcpy(rolenamebuf, roles[i].name.m);
+        if (roles[i].name.f) {
+            /* role has distinct name for female (C,P) */
+            if (gend == 1) {
+                /* female already chosen; replace male name */
+                Strcpy(rolenamebuf, roles[i].name.f);
+            } else if (gend < 0) {
+                /* not chosen yet; append slash+female name */
+                Strcat(rolenamebuf, "/");
+                Strcat(rolenamebuf, roles[i].name.f);
+            }
+        }
+        /* !filtering implies reset_role_filtering() where we want to
+           mark this role as preseleted if current filter excludes it */
+        add_menu(win, NO_GLYPH, &any, thisch, 0, ATR_NONE, an(rolenamebuf),
+                 (!filtering && !role_ok) ? MENU_SELECTED : MENU_UNSELECTED);
+        lastch = thisch;
+    }
+}
+
+STATIC_OVL void
+setup_racemenu(win, filtering, role, gend, algn)
+winid win;
+boolean filtering;
+int role, gend, algn;
+{
+    anything any;
+    boolean race_ok;
+    int i;
+    char this_ch;
+
+    any = zeroany;
+    for (i = 0; races[i].noun; i++) {
+        race_ok = ok_race(role, i, gend, algn);
+        if (filtering && !race_ok)
+            continue;
+        if (filtering)
+            any.a_int = i + 1;
+        else
+            any.a_string = races[i].noun;
+        this_ch = *races[i].noun;
+        /* filtering: picking race, so choose by first letter, with
+           capital letter as unseen accelerator;
+           !filtering: resetting filter rather than picking, choose by
+           capital letter since lowercase role letters will be present */
+        add_menu(win, NO_GLYPH, &any,
+                 filtering ? this_ch : highc(this_ch),
+                 filtering ? highc(this_ch) : 0,
+                 ATR_NONE, races[i].noun,
+                 (!filtering && !race_ok) ? MENU_SELECTED : MENU_UNSELECTED);
+    }
+}
+
+STATIC_DCL void
+setup_gendmenu(win, filtering, role, race, algn)
+winid win;
+boolean filtering;
+int role, race, algn;
+{
+    anything any;
+    boolean gend_ok;
+    int i;
+    char this_ch;
+
+    any = zeroany;
+    for (i = 0; i < ROLE_GENDERS; i++) {
+        gend_ok = ok_gend(role, race, i, algn);
+        if (filtering && !gend_ok)
+            continue;
+        if (filtering)
+            any.a_int = i + 1;
+        else
+            any.a_string = genders[i].adj;
+        this_ch = *genders[i].adj;
+        /* (see setup_racemenu for explanation of selector letters
+           and setup_rolemenu for preselection) */
+        add_menu(win, NO_GLYPH, &any,
+                 filtering ? this_ch : highc(this_ch),
+                 filtering ? highc(this_ch) : 0,
+                 ATR_NONE, genders[i].adj,
+                 (!filtering && !gend_ok) ? MENU_SELECTED : MENU_UNSELECTED);
+    }
+}
+
+STATIC_DCL void
+setup_algnmenu(win, filtering, role, race, gend)
+winid win;
+boolean filtering;
+int role, race, gend;
+{
+    anything any;
+    boolean algn_ok;
+    int i;
+    char this_ch;
+
+    any = zeroany;
+    for (i = 0; i < ROLE_ALIGNS; i++) {
+        algn_ok = ok_align(role, race, gend, i);
+        if (filtering && !algn_ok)
+            continue;
+        if (filtering)
+            any.a_int = i + 1;
+        else
+            any.a_string = aligns[i].adj;
+        this_ch = *aligns[i].adj;
+        /* (see setup_racemenu for explanation of selector letters
+           and setup_rolemenu for preselection) */
+        add_menu(win, NO_GLYPH, &any,
+                 filtering ? this_ch : highc(this_ch),
+                 filtering ? highc(this_ch) : 0,
+                 ATR_NONE, aligns[i].adj,
+                 (!filtering && !algn_ok) ? MENU_SELECTED : MENU_UNSELECTED);
+    }
+}
+
 /*
  * plname is filled either by an option (-u Player  or  -uPlayer) or
  * explicitly (by being the wizard) or by askname.