]> granicus.if.org Git - nethack/commitdiff
enhanced interactive role selection (trunk only)
authornethack.rankin <nethack.rankin>
Sun, 22 Mar 2009 00:23:57 +0000 (00:23 +0000)
committernethack.rankin <nethack.rankin>
Sun, 22 Mar 2009 00:23:57 +0000 (00:23 +0000)
 [See cvs log for src/role.c for a much longer description.]

     When picking role, race, and so forth, new menu entries allow you to
pick any of the other items before the one currently being handled.  After
picking all four of race, role, gender, and alignment (or if you answered
'y' to "shall I pick for you?"), there is a followup prompt to confirm the
choices.  It's a menu which also provides a chance to rename the character.

     This has only been implemented in win/tty's player_selection(), with
some support code in the core that might be useful to other interfaces.
And so far, the chance to rename is only presented as a menu choice if
you've given an answer to "who are you?" prompt earlier during startup.
Also, ports that use pcmain.c aren't able to perform hero renaming yet.

13 files changed:
doc/fixes35.0
include/extern.h
include/flag.h
include/winprocs.h
sys/be/bemain.c
sys/mac/macmain.c
sys/mac/macunix.c
sys/share/pcmain.c
sys/unix/unixmain.c
sys/unix/unixunix.c
sys/vms/vmsmain.c
sys/vms/vmsunix.c
win/tty/wintty.c

index 86873aa6a6ef5f1be9bb318be7e0c85dba89d109..f51df1bae1dcec9c819743cb14af0b9dea58b317 100644 (file)
@@ -448,6 +448,7 @@ win32gui: added menu options "Copy ASCII Screenshot To Clipboard" and "Save
 win32tty: support for 'selectsaved' option for menu of existing save files
        to choose from at game startup
 tty: add window port routines for saving/restoring message history
+tty: enhanced role, race, &c selection during at start of new game
 smartphone: added "Type Cmd" command that allows to type arbitrary commands 
        using phone keypad
 smartphone: added Q(quiver) command to "Attack" layout
index 811e7198084b59088eedf968a754883490aea255..ce93bc144d8a68655fa94a536004b3af3f1d56d7 100644 (file)
@@ -1965,14 +1965,16 @@ E boolean FDECL(ok_gend, (int, int, int, int));
 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(role_init);
 E void NDECL(rigid_role_checks);
+E boolean FDECL(setrolefilter, (char *));
+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);
+E void FDECL(role_selection_prolog, (int,winid));
+E void FDECL(role_menu_extra, (int,winid));
+E void NDECL(role_init);
 E const char *FDECL(Hello, (struct monst *));
 E const char *NDECL(Goodbye);
-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 boolean FDECL(setrolefilter, (char *));
 
 /* ### rumors.c ### */
 
index e45a374d38f2ccee6b7ac96d358ad8e01a02d7d5..de24b9501d89f466905418d689c84eb046646c40 100644 (file)
@@ -182,6 +182,8 @@ struct instance_flags {
        boolean  menu_tab_sep;  /* Use tabs to separate option menu fields */
        boolean  menu_requested; /* Flag for overloaded use of 'm' prefix
                                  * on some non-move commands */
+       boolean  renameallowed; /* can change hero name during role selection */
+       boolean  renameinprogress; /* we are changing hero name */
        boolean  zerocomp;      /* write zero-compressed save files */
        boolean  rlecomp;       /* run-length comp of levels when writing savefile */
        uchar    num_pad_mode;
index 11ad3ba284663112a5955176862a897e91415407..2623abaa7a0752d566836df1507726c7fe73d628 100644 (file)
@@ -254,4 +254,12 @@ struct wc_Opt {
        unsigned long wc_bit;
 };
 
+/* role selection by player_selection(); this ought to be in the core... */
+#define RS_NAME                0
+#define RS_ROLE                1
+#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 */
+
 #endif /* WINPROCS_H */
index 35dee04d357a5bc7e616d38c32eac2c8085f8f6d..3f67fc0c09acb346da0fbef4c0f3191bb37b5ce4 100644 (file)
@@ -44,14 +44,15 @@ int MAIN(int argc, char **argv)
        process_options(argc, argv);    /* command line options */
 
        set_playmode(); /* sets plname to "wizard" for wizard mode */
-       if(!*plname || !strncmp(plname, "player", 4)
-                   || !strncmp(plname, "games", 4))
-               askname();
-       plnamesuffix();         /* strip suffix from name; calls askname() */
-                                               /* again if suffix was whole name */
-                                               /* accepts any suffix */
-
-       Sprintf(lock,"%d%s", getuid(), plname);
+       /* strip role,race,&c suffix; calls askname() if plname[] is empty
+          or holds a generic user name like "player" or "games" */
+       plnamesuffix();
+       /* unlike Unix where the game might be invoked with a script
+          which forces a particular character name for each player
+          using a shared account, we always allow player to rename
+          the character during role/race/&c selection */
+       iflags.renameallowed = TRUE;
+
        getlock();
 
        dlb_init();                     /* must be before newgame() */
@@ -75,6 +76,11 @@ int MAIN(int argc, char **argv)
 
        display_gamewindows();
 
+       /*
+        * First, try to find and restore a save file for specified character.
+        * We'll return here if new game player_selection() renames the hero.
+        */
+ attempt_restore:
        if ((fd = restore_saved_game()) >= 0) {
 #ifdef NEWS
                if(iflags.news) {
@@ -99,7 +105,21 @@ int MAIN(int argc, char **argv)
        }
 
        if (!resuming) {
-               player_selection();
+               /* new game:  start by choosing role, race, etc;
+                  player might change the hero's name while doing that,
+                  in which case we try to restore under the new name
+                  and skip selection this time if that didn't succeed */
+               if (!iflags.renameinprogress) {
+                   player_selection();
+                   if (iflags.renameinprogress) {
+                       /* player has renamed the hero while selecting role;
+                          discard current lock file and create another for
+                          the new character name */
+                       delete_levelfile(0); /* remove empty lock file */
+                       getlock();
+                       goto attempt_restore;
+                   }
+               }
                newgame();
                if (discover)
                        You("are in non-scoring discovery mode.");
@@ -221,6 +241,7 @@ void getlock(void)
 {
        int fd;
 
+       Sprintf(lock, "%d%s", getuid(), plname);
        regularize(lock);
        set_levelfile_name(lock, 0);
        fd = creat(lock, FCMASK);
index 7a0d287a307ce21f6db19c891c2013e7e364d2d1..0ad68009e725e5bbaad5c94f697404b8aa131c68 100644 (file)
@@ -80,16 +80,22 @@ main (void)
        display_gamewindows();
 
        set_playmode(); /* sets plname to "wizard" for wizard mode */
-       if (!*plname || !strncmp(plname, "player", 4) ||
-           !strncmp(plname, "games", 4))
-               askname();
-       plnamesuffix();         /* strip suffix from name; calls askname() */
-                               /* again if suffix was whole name */
-                               /* accepts any suffix */
+       /* strip role,race,&c suffix; calls askname() if plname[] is empty
+          or holds a generic user name like "player" or "games" */
+       plnamesuffix();
+       /* unlike Unix where the game might be invoked with a script
+          which forces a particular character name for each player
+          using a shared account, we always allow player to rename
+          the character during role/race/&c selection */
+       iflags.renameallowed = TRUE;
 
-       Sprintf (lock, "%d%s", getuid (), plname);
-       getlock ();
+       getlock();
 
+       /*
+        * First, try to find and restore a save file for specified character.
+        * We'll return here if new game player_selection() renames the hero.
+        */
+ attempt_restore:
        if ((fd = restore_saved_game()) >= 0) {
 #ifdef NEWS
                if(iflags.news) {
@@ -102,6 +108,8 @@ main (void)
                game_active = 1;
                if (dorecover(fd)) {
                        resuming = TRUE;        /* not starting new game */
+                       if (discover)
+                               You("are in non-scoring discovery mode.");
                        if (discover || wizard) {
                                if(yn("Do you want to keep the save file?") == 'n')
                                        (void) delete_savefile();
@@ -113,14 +121,27 @@ main (void)
        }
 
        if (!resuming) {
-               player_selection();
+               /* new game:  start by choosing role, race, etc;
+                  player might change the hero's name while doing that,
+                  in which case we try to restore under the new name
+                  and skip selection this time if that didn't succeed */
+               if (!iflags.renameinprogress) {
+                   player_selection();
+                   if (iflags.renameinprogress) {
+                       /* player has renamed the hero while selecting role;
+                          discard current lock file and create another for
+                          the new character name */
+                       delete_levelfile(0); /* remove empty lock file */
+                       getlock();
+                       goto attempt_restore;
+                   }
+               }
                game_active = 1;        /* done with selection, draw active game window */
                newgame();
+               if (discover)
+                       You("are in non-scoring discovery mode.");
        }
 
-       if (discover)
-               You("are in non-scoring discovery mode.");
-
        UndimMenuBar (); /* Yes, this is the place for it (!) */
 
        moveloop(resuming);
index e29035fd366e6cba5acd381401a525cb066c2179..5e31dcdb9c4ea2a445321f99257bc97f44be7945 100644 (file)
@@ -22,7 +22,8 @@ getlock(void)
 {
        int fd;
        int pid = getpid(); /* Process ID */
-       
+
+       Sprintf(lock, "%d%s", getuid(), plname);
        set_levelfile_name (lock, 0);
 
        if ((fd = open (lock, O_RDWR | O_EXCL | O_CREAT, LEVL_TYPE)) == -1) {
index 36f140697bc4817a4acf4675022ab6834019af7b..bee05f9dcd7aa2e8d490077a3379e75aa793027f 100644 (file)
@@ -338,12 +338,23 @@ char *argv[];
 # endif
 #endif
 
-       if (!*plname)
-               askname();
-       plnamesuffix();         /* strip suffix from name; calls askname() */
-                               /* again if suffix was whole name */
-                               /* accepts any suffix */
+       /* strip role,race,&c suffix; calls askname() if plname[] is empty
+          or holds a generic user name like "player" or "games" */
+       plnamesuffix();
        set_playmode(); /* sets plname to "wizard" for wizard mode */
+#if 0
+       /* unlike Unix where the game might be invoked with a script
+          which forces a particular character name for each player
+          using a shared account, we always allow player to rename
+          the character during role/race/&c selection */
+       iflags.renameallowed = TRUE;
+#else
+ /* until the getlock code is resolved, override askname()'s
+    setting of renameallowed; when False, player_selection()
+    won't resent renaming as an option */
+ iflags.renameallowed = FALSE;
+#endif
+
 #if defined(PC_LOCKING)
        /* 3.3.0 added this to support detection of multiple games
         * under the same plname on the same machine in a windowed
@@ -427,6 +438,11 @@ char *argv[];
        getreturn_enabled = TRUE;
 #endif
 
+       /*
+        * First, try to find and restore a save file for specified character.
+        * We'll return here if new game player_selection() renames the hero.
+        */
+ attempt_restore:
        if ((fd = restore_saved_game()) >= 0) {
 #ifndef NO_SIGNAL
                (void) signal(SIGINT, (SIG_RET_TYPE) done1);
@@ -455,7 +471,23 @@ char *argv[];
        }
 
        if (!resuming) {
-               player_selection();
+               /* new game:  start by choosing role, race, etc;
+                  player might change the hero's name while doing that,
+                  in which case we try to restore under the new name
+                  and skip selection this time if that didn't succeed */
+               if (!iflags.renameinprogress) {
+                   player_selection();
+                   if (iflags.renameinprogress) {
+                       /* player has renamed the hero while selecting role;
+                          discard current lock file and create another for
+                          the new character name */
+#if 0       /* this needs to be reconciled with the getlock mess above... */
+                       delete_levelfile(0); /* remove empty lock file */
+                       getlock();
+#endif
+                       goto attempt_restore;
+                   }
+               }
                newgame();
                if (discover)
                        You("are in non-scoring discovery mode.");
index 4119d1e03eb540e981647f29c0e9166a19be74ef..81b033ceff427e8a758268c8969ce002f620af75 100644 (file)
@@ -192,10 +192,7 @@ char *argv[];
 
        /* wizard mode access is deferred until here */
        set_playmode(); /* sets plname to "wizard" for wizard mode */
-       if(!*plname || !strncmp(plname, "player", 4)
-                   || !strncmp(plname, "games", 4)) {
-               askname();
-       } else if (exact_username) {
+       if (exact_username) {
                /* guard against user names with hyphens in them */
                int len = strlen(plname);
                /* append the current role, if any, so that last dash is ours */
@@ -203,27 +200,33 @@ char *argv[];
                        (void)strncat(strcat(plname, "-"),
                                      pl_character, sizeof plname - len - 1);
        }
-       plnamesuffix();         /* strip suffix from name; calls askname() */
-                               /* again if suffix was whole name */
-                               /* accepts any suffix */
+       /* strip role,race,&c suffix; calls askname() if plname[] is empty
+          or holds a generic user name like "player" or "games" */
+       plnamesuffix();
+
 #ifdef WIZARD
-       if(!wizard) {
+       if (wizard) {
+               /* use character name rather than lock letter for file names */
+               locknum = 0;
+       } else
 #endif
-               /*
-                * check for multiple games under the same name
-                * (if !locknum) or check max nr of players (otherwise)
-                */
+           {
+               /* suppress interrupts while processing lock file */
                (void) signal(SIGQUIT,SIG_IGN);
                (void) signal(SIGINT,SIG_IGN);
-               if(!locknum)
-                       Sprintf(lock, "%d%s", (int)getuid(), plname);
-               getlock();
-#ifdef WIZARD
-       } else {
-               Sprintf(lock, "%d%s", (int)getuid(), plname);
-               getlock();
        }
-#endif /* WIZARD */
+       /*
+        * getlock() complains and quits if there is already a game
+        * in progress for current character name (when locknum == 0)
+        * or if there are too many active games (when locknum > 0).
+        * When proceeding, it creates an empty <lockname>.0 file to
+        * designate the current game.
+        * getlock() constructs <lockname> based on the character
+        * name (for !locknum) or on first available of alock, block,
+        * clock, &c not currently in use in the playground directory
+        * (for locknum > 0).
+        */
+       getlock();
        program_state.preserve_locks = 0;       /* after getlock() */
 
        dlb_init();     /* must be before newgame() */
@@ -247,6 +250,11 @@ char *argv[];
 
        display_gamewindows();
 
+       /*
+        * First, try to find and restore a save file for specified character.
+        * We'll return here if new game player_selection() renames the hero.
+        */
+ attempt_restore:
        if ((fd = restore_saved_game()) >= 0) {
                const char *fq_save = fqname(SAVEF, SAVEPREFIX, 1);
 
@@ -275,7 +283,24 @@ char *argv[];
        }
 
        if (!resuming) {
-               player_selection();
+               /* new game:  start by choosing role, race, etc;
+                  player might change the hero's name while doing that,
+                  in which case we try to restore under the new name
+                  and skip selection this time if that didn't succeed */
+               if (!iflags.renameinprogress) {
+                   player_selection();
+                   if (iflags.renameinprogress) {
+                       /* player has renamed the hero while selecting role;
+                          if locking alphabetically, the existing lock file
+                          can still be used; otherwise, discard current one
+                          and create another for the new character name */
+                       if (!locknum) {
+                           delete_levelfile(0); /* remove empty lock file */
+                           getlock();
+                       }
+                       goto attempt_restore;
+                   }
+               }
                newgame();
                wd_message();
        }
index d47ebe7c7ae6ba6805f665d7f3697f884dc09c47..c6dc21545c9d1696d595acaa0a3e82c1ba07d4f5 100644 (file)
@@ -112,6 +112,12 @@ getlock()
                error("%s", "");
        }
 
+       /* default value of lock[] is "1lock" where '1' gets changed to
+          'a','b',&c below; override the default and use <uid><charname>
+          if we aren't restricting the number of simultaneous games */
+       if (!locknum)
+               Sprintf(lock, "%u%s", (unsigned)getuid(), plname);
+
        regularize(lock);
        set_levelfile_name(lock, 0);
 
index 4ed280a8853c75eac64463fa7a3a224a1d7d25f6..9c64748919edf59c6ed98b6b6442fc92207d2c6c 100644 (file)
@@ -128,30 +128,33 @@ char *argv[];
 
        /* wizard mode access is deferred until here */
        set_playmode(); /* sets plname to "wizard" for wizard mode */
-       if (!*plname || !strncmpi(plname, "games", 4) ||
-           !strcmpi(plname, "nethack"))
-               askname();
-       plnamesuffix();         /* strip suffix from name; calls askname() */
-                               /* again if suffix was whole name */
-                               /* accepts any suffix */
+       /* strip role,race,&c suffix; calls askname() if plname[] is empty
+          or holds a generic user name like "player" or "games" */
+       plnamesuffix();
+
 #ifdef WIZARD
-       if(!wizard) {
+       if (wizard) {
+               /* use character name rather than lock letter for file names */
+               locknum = 0;
+       } else
 #endif
-               /*
-                * check for multiple games under the same name
-                * (if !locknum) or check max nr of players (otherwise)
-                */
-               (void) signal(SIGQUIT,SIG_IGN);
-               (void) signal(SIGINT,SIG_IGN);
-               if(!locknum)
-                       Sprintf(lock, "_%u%s", (unsigned)getuid(), plname);
-               getlock();
-#ifdef WIZARD
-       } else {
-               Sprintf(lock, "_%u%s", (unsigned)getuid(), plname);
-               getlock();
+           {
+               /* suppress interrupts while processing lock file */
+               (void) signal(SIGQUIT, SIG_IGN);
+               (void) signal(SIGINT, SIG_IGN);
        }
-#endif /* WIZARD */
+       /*
+        * getlock() complains and quits if there is already a game
+        * in progress for current character name (when locknum == 0)
+        * or if there are too many active games (when locknum > 0).
+        * When proceeding, it creates an empty <lockname>.0 file to
+        * designate the current game.
+        * getlock() constructs <lockname> based on the character
+        * name (for !locknum) or on first available of alock, block,
+        * clock, &c not currently in use in the playground directory
+        * (for locknum > 0).
+        */
+       getlock();
 
        dlb_init();     /* must be before newgame() */
 
@@ -174,6 +177,11 @@ char *argv[];
 
        display_gamewindows();
 
+       /*
+        * First, try to find and restore a save file for specified character.
+        * We'll return here if new game player_selection() renames the hero.
+        */
+ attempt_restore:
        if ((fd = restore_saved_game()) >= 0) {
                const char *fq_save = fqname(SAVEF, SAVEPREFIX, 1);
 
@@ -200,7 +208,24 @@ char *argv[];
        }
 
        if (!resuming) {
-               player_selection();
+               /* new game:  start by choosing role, race, etc;
+                  player might change the hero's name while doing that,
+                  in which case we try to restore under the new name
+                  and skip selection this time if that didn't succeed */
+               if (!iflags.renameinprogress) {
+                   player_selection();
+                   if (iflags.renameinprogress) {
+                       /* player has renamed the hero while selecting role;
+                          if locking alphabetically, the existing lock file
+                          can still be used; otherwise, discard current one
+                          and create another for the new character name */
+                       if (!locknum) {
+                           delete_levelfile(0); /* remove empty lock file */
+                           getlock();
+                       }
+                       goto attempt_restore;
+                   }
+               }
                newgame();
                wd_message();
        }
index 24fefaa0de2a55fce30ece5c16da3bb55d8ff723..3b7dab480db0f7b2af486dde7e78a7000a12b111 100644 (file)
@@ -94,6 +94,12 @@ getlock()
                error("Quitting.");
        }
 
+       /* default value of lock[] is "1lock" where '1' gets changed to
+          'a','b',&c below; override the default and use <uid><charname>
+          if we aren't restricting the number of simultaneous games */
+       if (!locknum)
+               Sprintf(lock, "_%u%s", (unsigned)getuid(), plname);
+
        regularize(lock);
        set_levelfile_name(lock, 0);
        if(locknum > 25) locknum = 25;
index f946e519a3a5fbdbc5ff4c61126eeaa11d364906..7d7e614f9007cc69233dd164fce59b567d4d7ec6 100644 (file)
@@ -353,343 +353,509 @@ char** argv;
     tty_display_nhwindow(BASE_WINDOW, FALSE);
 }
 
+/* try to reduce clutter in the code below... */
+#define ROLE flags.initrole
+#define RACE flags.initrace
+#define GEND flags.initgend
+#define ALGN flags.initalign
+
 void
 tty_player_selection()
 {
-       int i, k, n;
-       char pick4u = 'n', thisch, lastch = 0;
-       char pbuf[QBUFSZ], plbuf[QBUFSZ];
-       winid win;
-       anything any;
-       menu_item *selected = 0;
-
-       /* prevent an unnecessary prompt */
-       rigid_role_checks();
-
-       /* Should we randomly pick for the player? */
-       if (!flags.randomall &&
-           (flags.initrole == ROLE_NONE || flags.initrace == ROLE_NONE ||
-            flags.initgend == ROLE_NONE || flags.initalign == ROLE_NONE)) {
-           int echoline;
-           char *prompt = build_plselection_prompt(pbuf, QBUFSZ, flags.initrole,
-                               flags.initrace, flags.initgend, flags.initalign);
+    int i, k, n, choice, nextpick;
+    boolean getconfirmation;
+    char pick4u = 'n', thisch, lastch = 0;
+    char pbuf[QBUFSZ], plbuf[QBUFSZ];
+    winid win;
+    anything any;
+    menu_item *selected = 0;
+
+    if (flags.randomall) {
+       if (ROLE == ROLE_NONE) ROLE = ROLE_RANDOM;
+       if (RACE == ROLE_NONE) RACE = ROLE_RANDOM;
+       if (GEND == ROLE_NONE) GEND = ROLE_RANDOM;
+       if (ALGN == ROLE_NONE) ALGN = ROLE_RANDOM;
+    }
 
+    /* prevent an unnecessary prompt */
+    rigid_role_checks();
+
+    /* Should we randomly pick for the player? */
+    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);
+
+       /* this prompt string ends in "[ynaq]?":
+          y - game picks role,&c then asks player to confirm;
+          n - player manually chooses via menu selections;
+          a - like 'y', but skips confirmation and starts game;
+          q - quit
+        */
+       tty_putstr(BASE_WINDOW, 0, "");
+       echoline = wins[BASE_WINDOW]->cury;
+       tty_putstr(BASE_WINDOW, 0, prompt);
+       do {
+           pick4u = lowc(readchar());
+           if (index(quitchars, pick4u)) pick4u = 'y';
+       } while (!index(ynaqchars, pick4u));
+       if ((int)strlen(prompt) + 1 < CO) {
+           /* Echo choice and move back down line */
+           tty_putsym(BASE_WINDOW, (int)strlen(prompt)+1, echoline, pick4u);
            tty_putstr(BASE_WINDOW, 0, "");
-           echoline = wins[BASE_WINDOW]->cury;
-           tty_putstr(BASE_WINDOW, 0, prompt);
-           do {
-               pick4u = lowc(readchar());
-               if (index(quitchars, pick4u)) pick4u = 'y';
-           } while(!index(ynqchars, pick4u));
-           if ((int)strlen(prompt) + 1 < CO) {
-               /* Echo choice and move back down line */
-               tty_putsym(BASE_WINDOW, (int)strlen(prompt)+1, echoline, pick4u);
-               tty_putstr(BASE_WINDOW, 0, "");
-           } else
-               /* Otherwise it's hard to tell where to echo, and things are
-                * wrapping a bit messily anyway, so (try to) make sure the next
-                * question shows up well and doesn't get wrapped at the
-                * bottom of the window.
-                */
-               tty_clear_nhwindow(BASE_WINDOW);
-           
-           if (pick4u != 'y' && pick4u != 'n') {
-give_up:       /* Quit */
-               if (selected) free((genericptr_t) selected);
-               bail((char *)0);
-               /*NOTREACHED*/
-               return;
-           }
-       }
-
-       (void)  root_plselection_prompt(plbuf, QBUFSZ - 1,
-                       flags.initrole, flags.initrace, flags.initgend, flags.initalign);
-
-       /* Select a role, if necessary */
-       /* we'll try to be compatible with pre-selected race/gender/alignment,
-        * but may not succeed */
-       if (flags.initrole < 0) {
-           char rolenamebuf[QBUFSZ];
-           /* Process the choice */
-           if (pick4u == 'y' || flags.initrole == ROLE_RANDOM || flags.randomall) {
-               /* Pick a random role */
-               flags.initrole = pick_role(flags.initrace, flags.initgend,
-                                               flags.initalign, PICK_RANDOM);
-               if (flags.initrole < 0) {
-                   tty_putstr(BASE_WINDOW, 0, "Incompatible role!");
-                   flags.initrole = randrole();
-               }
-           } else {
-               tty_clear_nhwindow(BASE_WINDOW);
-               tty_putstr(BASE_WINDOW, 0, "Choosing Character's Role");
-               /* Prompt for a role */
-               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, flags.initrace, flags.initgend,
-                                                       flags.initalign)) {
-                       any.a_int = i+1;        /* must be non-zero */
-                       thisch = lowc(roles[i].name.m[0]);
-                       if (thisch == lastch) thisch = highc(thisch);
-                       if (flags.initgend != ROLE_NONE && flags.initgend != ROLE_RANDOM) {
-                               if (flags.initgend == 1  && roles[i].name.f)
-                                       Strcpy(rolenamebuf, roles[i].name.f);
-                               else
-                                       Strcpy(rolenamebuf, roles[i].name.m);
-                       } else {
-                               if (roles[i].name.f) {
-                                       Strcpy(rolenamebuf, roles[i].name.m);
-                                       Strcat(rolenamebuf, "/");
-                                       Strcat(rolenamebuf, roles[i].name.f);
-                               } else 
-                                       Strcpy(rolenamebuf, roles[i].name.m);
-                       }       
-                       add_menu(win, NO_GLYPH, &any, thisch,
-                           0, ATR_NONE, an(rolenamebuf), MENU_UNSELECTED);
-                       lastch = thisch;
-                   }
-               }
-               any.a_int = pick_role(flags.initrace, flags.initgend,
-                                   flags.initalign, PICK_RANDOM)+1;
-               if (any.a_int == 0)     /* must be non-zero */
-                   any.a_int = randrole()+1;
-               add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE,
-                               "Random", MENU_UNSELECTED);
-               any.a_int = i+1;        /* must be non-zero */
-               add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE,
-                               "Quit", MENU_UNSELECTED);
-               Sprintf(pbuf, "Pick a role for your %s", plbuf);
-               end_menu(win, pbuf);
-               n = select_menu(win, PICK_ONE, &selected);
-               destroy_nhwindow(win);
+       } else
+           /* Otherwise it's hard to tell where to echo, and things are
+            * wrapping a bit messily anyway, so (try to) make sure the next
+            * question shows up well and doesn't get wrapped at the
+            * bottom of the window.
+            */
+           tty_clear_nhwindow(BASE_WINDOW);
+
+       if (pick4u != 'y' && pick4u != 'a' && pick4u != 'n') goto give_up;
+    }
 
+ makepicks:
+    nextpick = RS_ROLE;
+    do {
+       if (nextpick == RS_ROLE) {
+           nextpick = RS_RACE;
+           /* Select a role, if necessary;
+              we'll try to be compatible with pre-selected
+              race/gender/alignment, but may not succeed. */
+           if (ROLE < 0) {
                /* Process the choice */
-               if (n != 1 || selected[0].item.a_int == any.a_int)
-                   goto give_up;               /* Selected quit */
-
-               flags.initrole = selected[0].item.a_int - 1;
-               free((genericptr_t) selected),  selected = 0;
-           }
-           (void)  root_plselection_prompt(plbuf, QBUFSZ - 1,
-                       flags.initrole, flags.initrace, flags.initgend, flags.initalign);
-       }
-       
-       /* Select a race, if necessary */
-       /* force compatibility with role, try for compatibility with
-        * pre-selected gender/alignment */
-       if (flags.initrace < 0 || !validrace(flags.initrole, flags.initrace)) {
-           /* pre-selected race not valid */
-           if (pick4u == 'y' || flags.initrace == ROLE_RANDOM || flags.randomall) {
-               flags.initrace = pick_race(flags.initrole, flags.initgend,
-                                                       flags.initalign, PICK_RANDOM);
-               if (flags.initrace < 0) {
-                   tty_putstr(BASE_WINDOW, 0, "Incompatible race!");
-                   flags.initrace = randrace(flags.initrole);
-               }
-           } else {    /* pick4u == 'n' */
-               /* Count the number of valid races */
-               n = 0;  /* number valid */
-               k = 0;  /* valid race */
-               for (i = 0; races[i].noun; i++) {
-                   if (ok_race(flags.initrole, i, flags.initgend,
-                                                       flags.initalign)) {
-                       n++;
-                       k = i;
-                   }
-               }
-               if (n == 0) {
-                   for (i = 0; races[i].noun; i++) {
-                       if (validrace(flags.initrole, i)) {
-                           n++;
-                           k = i;
-                       }
+               if (pick4u == 'y' || pick4u == 'a' || ROLE == ROLE_RANDOM) {
+                   /* Pick a random role */
+                   k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
+                   if (k < 0) {
+                       tty_putstr(BASE_WINDOW, 0, "Incompatible role!");
+                       k = randrole();
                    }
-               }
+               } else {
+                   /* Prompt for a role */
+                   char rolenamebuf[QBUFSZ];
 
-               /* Permit the user to pick, if there is more than one */
-               if (n > 1) {
                    tty_clear_nhwindow(BASE_WINDOW);
-                   tty_putstr(BASE_WINDOW, 0, "Choosing Race");
+                   role_selection_prolog(RS_ROLE, BASE_WINDOW);
                    win = create_nhwindow(NHW_MENU);
                    start_menu(win);
-                   any = zeroany;         /* zero out all bits */
-                   for (i = 0; races[i].noun; i++)
-                       if (ok_race(flags.initrole, i, flags.initgend,
-                                                       flags.initalign)) {
-                           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);
+                   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);
+                           }
                        }
-                   any.a_int = pick_race(flags.initrole, flags.initgend,
-                                       flags.initalign, PICK_RANDOM)+1;
-                   if (any.a_int == 0) /* must be non-zero */
-                       any.a_int = randrace(flags.initrole)+1;
-                   add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE,
-                                   "Random", MENU_UNSELECTED);
-                   any.a_int = i+1;    /* must be non-zero */
-                   add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE,
-                                   "Quit", MENU_UNSELECTED);
-                   Sprintf(pbuf, "Pick the race of your %s", plbuf);
+                       add_menu(win, NO_GLYPH, &any, thisch, 0, ATR_NONE,
+                                an(rolenamebuf), MENU_UNSELECTED);
+                       lastch = thisch;
+                   }
+                   role_menu_extra(ROLE_RANDOM, win);
+                   any.a_int = 0;      /* separator, not a choice */
+                   add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE,
+                            "", MENU_UNSELECTED);
+                   role_menu_extra(RS_RACE, win);
+                   role_menu_extra(RS_GENDER, win);
+                   role_menu_extra(RS_ALGNMNT, win);
+                   role_menu_extra(ROLE_NONE, win);            /* quit */
+                   Strcpy(pbuf, "Pick a role or profession");
                    end_menu(win, pbuf);
                    n = select_menu(win, PICK_ONE, &selected);
+                   choice = (n == 1) ? selected[0].item.a_int : ROLE_NONE;
+                   if (selected) free((genericptr_t) selected), selected = 0;
                    destroy_nhwindow(win);
-                   if (n != 1 || selected[0].item.a_int == any.a_int)
-                       goto give_up;           /* Selected quit */
-
-                   k = selected[0].item.a_int - 1;
-                   free((genericptr_t) selected),      selected = 0;
-               }
-               flags.initrace = k;
-           }
-           (void)  root_plselection_prompt(plbuf, QBUFSZ - 1,
-                       flags.initrole, flags.initrace, flags.initgend, flags.initalign);
-       }
 
-       /* Select a gender, if necessary */
-       /* force compatibility with role/race, try for compatibility with
-        * pre-selected alignment */
-       if (flags.initgend < 0 || !validgend(flags.initrole, flags.initrace,
-                                               flags.initgend)) {
-           /* pre-selected gender not valid */
-           if (pick4u == 'y' || flags.initgend == ROLE_RANDOM || flags.randomall) {
-               flags.initgend = pick_gend(flags.initrole, flags.initrace,
-                                               flags.initalign, PICK_RANDOM);
-               if (flags.initgend < 0) {
-                   tty_putstr(BASE_WINDOW, 0, "Incompatible gender!");
-                   flags.initgend = randgend(flags.initrole, flags.initrace);
-               }
-           } else {    /* pick4u == 'n' */
-               /* Count the number of valid genders */
-               n = 0;  /* number valid */
-               k = 0;  /* valid gender */
-               for (i = 0; i < ROLE_GENDERS; i++) {
-                   if (ok_gend(flags.initrole, flags.initrace, i,
-                                                       flags.initalign)) {
-                       n++;
-                       k = i;
+                   if (choice == ROLE_NONE) {
+                       goto give_up;           /* Selected quit */
+                   } else if (choice == RS_menu_arg(RS_ALGNMNT)) {
+                       ALGN = k = ROLE_NONE;
+                       nextpick = RS_ALGNMNT;
+                   } else if (choice == RS_menu_arg(RS_GENDER)) {
+                       GEND = k = ROLE_NONE;
+                       nextpick = RS_GENDER;
+                   } else if (choice == RS_menu_arg(RS_RACE)) {
+                       RACE = k = ROLE_NONE;
+                       nextpick = RS_RACE;
+                   } else if (choice == ROLE_RANDOM) {
+                       k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
+                       if (k < 0) k = randrole();
+                   } else {
+                       k = choice - 1;
                    }
                }
-               if (n == 0) {
-                   for (i = 0; i < ROLE_GENDERS; i++) {
-                       if (validgend(flags.initrole, flags.initrace, i)) {
+               ROLE = k;
+           } /* needed role */
+       } /* picking role */
+
+       if (nextpick == RS_RACE) {
+           nextpick = (ROLE < 0) ? RS_ROLE : RS_GENDER;
+           /* Select a race, if necessary;
+              force compatibility with role, try for compatibility
+              with pre-selected gender/alignment. */
+           if (RACE < 0 || !validrace(ROLE, RACE)) {
+               /* no race yet, or pre-selected race not valid */
+               if (pick4u == 'y' || pick4u == 'a' || RACE == ROLE_RANDOM) {
+                   k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
+                   if (k < 0) {
+                       tty_putstr(BASE_WINDOW, 0, "Incompatible race!");
+                       k = randrace(ROLE);
+                   }
+               } else {        /* pick4u == 'n' */
+                   /* Count the number of valid races */
+                   n = 0;      /* number valid */
+                   k = 0;      /* valid race */
+                   for (i = 0; races[i].noun; i++)
+                       if (ok_race(ROLE, i, GEND, ALGN)) {
                            n++;
                            k = i;
                        }
+                   if (n == 0) {
+                       for (i = 0; races[i].noun; i++)
+                           if (validrace(ROLE, i)) {
+                               n++;
+                               k = i;
+                           }
+                   }
+                   /* Permit the user to pick, if there is more than one */
+                   if (n > 1) {
+                       tty_clear_nhwindow(BASE_WINDOW);
+                       role_selection_prolog(RS_RACE, BASE_WINDOW);
+                       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);
+                       }
+                       role_menu_extra(ROLE_RANDOM, win);
+                       any.a_int = 0;          /* separator, not a choice */
+                       add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE,
+                                "", MENU_UNSELECTED);
+                       role_menu_extra(RS_ROLE, win);
+                       role_menu_extra(RS_GENDER, win);
+                       role_menu_extra(RS_ALGNMNT, 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;
+                       if (selected) free((genericptr_t) selected), selected = 0;
+                       destroy_nhwindow(win);
+
+                       if (choice == ROLE_NONE) {
+                           goto give_up;               /* Selected quit */
+                       } else if (choice == RS_menu_arg(RS_ALGNMNT)) {
+                           ALGN = k = ROLE_NONE;
+                           nextpick = RS_ALGNMNT;
+                       } else if (choice == RS_menu_arg(RS_GENDER)) {
+                           GEND = k = ROLE_NONE;
+                           nextpick = RS_GENDER;
+                       } else if (choice == RS_menu_arg(RS_ROLE)) {
+                           ROLE = k = ROLE_NONE;
+                           nextpick = RS_ROLE;
+                       } else if (choice == ROLE_RANDOM) {
+                           k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
+                           if (k < 0) k = randrace(ROLE);
+                       } else {
+                           k = choice - 1;
+                       }
                    }
                }
-
-               /* Permit the user to pick, if there is more than one */
-               if (n > 1) {
-                   tty_clear_nhwindow(BASE_WINDOW);
-                   tty_putstr(BASE_WINDOW, 0, "Choosing Gender");
-                   win = create_nhwindow(NHW_MENU);
-                   start_menu(win);
-                   any = zeroany;         /* zero out all bits */
+               RACE = k;
+           } /* needed race */
+       } /* picking race */
+
+       if (nextpick == RS_GENDER) {
+           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. */
+           if (GEND < 0 || !validgend(ROLE, RACE, GEND)) {
+               /* no gender yet, or pre-selected gender not valid */
+               if (pick4u == 'y' || pick4u == 'a' || GEND == ROLE_RANDOM) {
+                   k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
+                   if (k < 0) {
+                       tty_putstr(BASE_WINDOW, 0, "Incompatible gender!");
+                       k = randgend(ROLE, RACE);
+                   }
+               } else {        /* pick4u == 'n' */
+                   /* Count the number of valid genders */
+                   n = 0;      /* number valid */
+                   k = 0;      /* valid gender */
                    for (i = 0; i < ROLE_GENDERS; i++)
-                       if (ok_gend(flags.initrole, flags.initrace, i,
-                                                           flags.initalign)) {
-                           any.a_int = i+1;
+                       if (ok_gend(ROLE, RACE, i, ALGN)) {
+                           n++;
+                           k = i;
+                       }
+                   if (n == 0) {
+                       for (i = 0; i < ROLE_GENDERS; i++)
+                           if (validgend(ROLE, RACE, i)) {
+                               n++;
+                               k = i;
+                           }
+                   }
+                   /* Permit the user to pick, if there is more than one */
+                   if (n > 1) {
+                       tty_clear_nhwindow(BASE_WINDOW);
+                       role_selection_prolog(RS_GENDER, BASE_WINDOW);
+                       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);
+                                0, ATR_NONE, genders[i].adj, MENU_UNSELECTED);
+                       }
+                       role_menu_extra(ROLE_RANDOM, win);
+                       any.a_int = 0;          /* separator, not a choice */
+                       add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE,
+                                "", MENU_UNSELECTED);
+                       role_menu_extra(RS_ROLE, win);
+                       role_menu_extra(RS_RACE, win);
+                       role_menu_extra(RS_ALGNMNT, 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;
+                       if (selected) free((genericptr_t) selected), selected = 0;
+                       destroy_nhwindow(win);
+
+                       if (choice == ROLE_NONE) {
+                           goto give_up;               /* Selected quit */
+                       } else if (choice == RS_menu_arg(RS_ALGNMNT)) {
+                           ALGN = k = ROLE_NONE;
+                           nextpick = RS_ALGNMNT;
+                       } else if (choice == RS_menu_arg(RS_RACE)) {
+                           RACE = k = ROLE_NONE;
+                           nextpick = RS_RACE;
+                       } else if (choice == RS_menu_arg(RS_ROLE)) {
+                           ROLE = k = ROLE_NONE;
+                           nextpick = RS_ROLE;
+                       } else if (choice == ROLE_RANDOM) {
+                           k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
+                           if (k < 0) k = randgend(ROLE, RACE);
+                       } else {
+                           k = choice - 1;
                        }
-                   any.a_int = pick_gend(flags.initrole, flags.initrace,
-                                           flags.initalign, PICK_RANDOM)+1;
-                   if (any.a_int == 0) /* must be non-zero */
-                       any.a_int = randgend(flags.initrole, flags.initrace)+1;
-                   add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE,
-                                   "Random", MENU_UNSELECTED);
-                   any.a_int = i+1;    /* must be non-zero */
-                   add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE,
-                                   "Quit", MENU_UNSELECTED);
-                   Sprintf(pbuf, "Pick the gender of your %s", plbuf);
-                   end_menu(win, pbuf);
-                   n = select_menu(win, PICK_ONE, &selected);
-                   destroy_nhwindow(win);
-                   if (n != 1 || selected[0].item.a_int == any.a_int)
-                       goto give_up;           /* Selected quit */
-
-                   k = selected[0].item.a_int - 1;
-                   free((genericptr_t) selected),      selected = 0;
-               }
-               flags.initgend = k;
-           }
-           (void)  root_plselection_prompt(plbuf, QBUFSZ - 1,
-                       flags.initrole, flags.initrace, flags.initgend, flags.initalign);
-       }
-
-       /* Select an alignment, if necessary */
-       /* force compatibility with role/race/gender */
-       if (flags.initalign < 0 || !validalign(flags.initrole, flags.initrace,
-                                                       flags.initalign)) {
-           /* pre-selected alignment not valid */
-           if (pick4u == 'y' || flags.initalign == ROLE_RANDOM || flags.randomall) {
-               flags.initalign = pick_align(flags.initrole, flags.initrace,
-                                                       flags.initgend, PICK_RANDOM);
-               if (flags.initalign < 0) {
-                   tty_putstr(BASE_WINDOW, 0, "Incompatible alignment!");
-                   flags.initalign = randalign(flags.initrole, flags.initrace);
-               }
-           } else {    /* pick4u == 'n' */
-               /* Count the number of valid alignments */
-               n = 0;  /* number valid */
-               k = 0;  /* valid alignment */
-               for (i = 0; i < ROLE_ALIGNS; i++) {
-                   if (ok_align(flags.initrole, flags.initrace, flags.initgend,
-                                                       i)) {
-                       n++;
-                       k = i;
                    }
                }
-               if (n == 0) {
-                   for (i = 0; i < ROLE_ALIGNS; i++) {
-                       if (validalign(flags.initrole, flags.initrace, i)) {
+               GEND = k;
+           } /* needed gender */
+       } /* picking gender */
+
+       if (nextpick == RS_ALGNMNT) {
+           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)) {
+               /* no alignment yet, or pre-selected alignment not valid */
+               if (pick4u == 'y' || pick4u == 'a' || ALGN == ROLE_RANDOM) {
+                   k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
+                   if (k < 0) {
+                       tty_putstr(BASE_WINDOW, 0, "Incompatible alignment!");
+                       k = randalign(ROLE, RACE);
+                   }
+               } else {        /* pick4u == 'n' */
+                   /* Count the number of valid alignments */
+                   n = 0;      /* number valid */
+                   k = 0;      /* valid alignment */
+                   for (i = 0; i < ROLE_ALIGNS; i++)
+                       if (ok_align(ROLE, RACE, GEND, i)) {
                            n++;
                            k = i;
                        }
+                   if (n == 0) {
+                       for (i = 0; i < ROLE_ALIGNS; i++)
+                           if (validalign(ROLE, RACE, i)) {
+                               n++;
+                               k = i;
+                           }
                    }
-               }
-
-               /* Permit the user to pick, if there is more than one */
-               if (n > 1) {
-                   tty_clear_nhwindow(BASE_WINDOW);
-                   tty_putstr(BASE_WINDOW, 0, "Choosing Alignment");
-                   win = create_nhwindow(NHW_MENU);
-                   start_menu(win);
-                   any = zeroany;         /* zero out all bits */
-                   for (i = 0; i < ROLE_ALIGNS; i++)
-                       if (ok_align(flags.initrole, flags.initrace,
-                                                       flags.initgend, i)) {
-                           any.a_int = i+1;
+                   /* Permit the user to pick, if there is more than one */
+                   if (n > 1) {
+                       tty_clear_nhwindow(BASE_WINDOW);
+                       role_selection_prolog(RS_ALGNMNT, BASE_WINDOW);
+                       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);
+                                 0, ATR_NONE, aligns[i].adj, MENU_UNSELECTED);
                        }
-                   any.a_int = pick_align(flags.initrole, flags.initrace,
-                                           flags.initgend, PICK_RANDOM)+1;
-                   if (any.a_int == 0) /* must be non-zero */
-                       any.a_int = randalign(flags.initrole, flags.initrace)+1;
-                   add_menu(win, NO_GLYPH, &any , '*', 0, ATR_NONE,
-                                   "Random", MENU_UNSELECTED);
-                   any.a_int = i+1;    /* must be non-zero */
-                   add_menu(win, NO_GLYPH, &any , 'q', 0, ATR_NONE,
-                                   "Quit", MENU_UNSELECTED);
-                   Sprintf(pbuf, "Pick the alignment of your %s", plbuf);
-                   end_menu(win, pbuf);
-                   n = select_menu(win, PICK_ONE, &selected);
-                   destroy_nhwindow(win);
-                   if (n != 1 || selected[0].item.a_int == any.a_int)
-                       goto give_up;           /* Selected quit */
-
-                   k = selected[0].item.a_int - 1;
-                   free((genericptr_t) selected),      selected = 0;
+                       role_menu_extra(ROLE_RANDOM, win);
+                       any.a_int = 0;          /* separator, not a choice */
+                       add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE,
+                                "", MENU_UNSELECTED);
+                       role_menu_extra(RS_ROLE, win);
+                       role_menu_extra(RS_RACE, win);
+                       role_menu_extra(RS_GENDER, 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;
+                       if (selected) free((genericptr_t) selected), selected = 0;
+                       destroy_nhwindow(win);
+
+                       if (choice == ROLE_NONE) {
+                           goto give_up;               /* Selected quit */
+                       } else if (choice == RS_menu_arg(RS_GENDER)) {
+                           GEND = k = ROLE_NONE;
+                           nextpick = RS_GENDER;
+                       } else if (choice == RS_menu_arg(RS_RACE)) {
+                           RACE = k = ROLE_NONE;
+                           nextpick = RS_RACE;
+                       } else if (choice == RS_menu_arg(RS_ROLE)) {
+                           ROLE = k = ROLE_NONE;
+                           nextpick = RS_ROLE;
+                       } else if (choice == ROLE_RANDOM) {
+                           k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
+                           if (k < 0) k = randalign(ROLE, RACE);
+                       } else {
+                           k = choice - 1;
+                       }
+                   }
                }
-               flags.initalign = k;
-           }
+               ALGN = k;
+           } /* needed alignment */
+       } /* picking alignment */
+
+    } while (ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0);
+
+    /*
+     * Role, race, &c have now been determined;
+     * ask for confirmation and maybe go back to choose all over again.
+     *
+     * Uses ynaq for familiarity, although 'a' is usually a
+     * superset of 'y' but here is an alternate form of 'n'.
+     * Menu layout:
+     *  title:  Is this ok? [ynaq]
+     *  blank:
+     *   text:  $name, $alignment $gender $race $role
+     *  blank:
+     *   menu:  y + yes; play
+     *          n - no; pick again
+     *  maybe:  a - no; rename hero
+     *          q - quit
+     *          (end)
+     */
+    getconfirmation = (pick4u != 'a' && !flags.randomall);
+    while (getconfirmation) {
+       tty_clear_nhwindow(BASE_WINDOW);
+       role_selection_prolog(ROLE_NONE, BASE_WINDOW);
+       win = create_nhwindow(NHW_MENU);
+       start_menu(win);
+       any = zeroany;          /* zero out all bits */
+       any.a_int = 0;
+       if (!roles[ROLE].name.f &&
+               (roles[ROLE].allow & ROLE_GENDMASK) == (ROLE_MALE|ROLE_FEMALE))
+           Sprintf(plbuf, " %s", genders[GEND].adj);
+       else
+           *plbuf = '\0';      /* omit redundant gender */
+       Sprintf(pbuf, "%s, %s%s %s %s",
+               plname, aligns[ALGN].adj, plbuf, races[FACE].adj,
+               (GEND == 1 && roles[ROLE].name.f) ?
+                   roles[ROLE].name.f : roles[ROLE].name.m);
+       add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE,
+                pbuf, MENU_UNSELECTED);
+       /* blank separator */
+       any.a_int = 0;
+       add_menu(win, NO_GLYPH, &any, ' ', 0, ATR_NONE,
+                "", MENU_UNSELECTED);
+       /* [ynaq] menu choices */
+       any.a_int = 1;
+       add_menu(win, NO_GLYPH, &any, 'y', 0, ATR_NONE,
+                "Yes; start game", MENU_SELECTED);
+       any.a_int = 2;
+       add_menu(win, NO_GLYPH, &any, 'n', 0, ATR_NONE,
+                "No; choose role again", MENU_UNSELECTED);
+       if (iflags.renameallowed) {
+           any.a_int = 3;
+           add_menu(win, NO_GLYPH, &any, 'a', 0, ATR_NONE,
+                    "Not yet; choose another name", MENU_UNSELECTED);
        }
-       /* Success! */
-       tty_display_nhwindow(BASE_WINDOW, FALSE);
+       any.a_int = -1;
+       add_menu(win, NO_GLYPH, &any, 'q', 0, ATR_NONE,
+                "Quit", MENU_UNSELECTED);
+       Sprintf(pbuf, "Is this ok? [yn%sq]", iflags.renameallowed ? "a" : "");
+       end_menu(win, pbuf);
+       n = select_menu(win, PICK_ONE, &selected);
+       /* [pick-one menus with a preselected entry behave oddly...] */
+       choice = (n > 0) ? selected[n - 1].item.a_int : (n == 0) ? 1 : -1;
+       if (selected) free((genericptr_t) selected), selected = 0;
+
+       switch (choice) {
+       default:        /* 'q' or ESC */
+           goto give_up;       /* quit */
+           break;
+       case 3:         /* 'a' */
+    /*
+     * TODO: what, if anything, should be done if the name is
+     * changed to or from "wizard" after port-specific startup
+     * code has set flags.debug based on the original name?
+     */
+         {
+           int saveROLE, saveRACE, saveGEND, saveALGN;
+
+           iflags.renameinprogress = TRUE;
+           /* 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;
+           *plname = '\0';
+           plnamesuffix();     /* calls askname() when plname[] is empty */
+           ROLE = saveROLE, RACE = saveRACE, GEND = saveGEND, ALGN = saveALGN;
+         }
+           break;      /* getconfirmation is still True */
+       case 2:         /* 'n' */
+           /* start fresh, but bypass "shall I pick everything for you?"
+              step; any partial role selection via config file, command
+              line, or name suffix is discarded this time */
+           pick4u = 'n';
+           ROLE = RACE = GEND = ALGN = ROLE_NONE;
+           goto makepicks;
+           break;
+       case 1:         /* 'y' or Space or Return/Enter */
+           /* success; drop out through end of function */
+           getconfirmation = FALSE;
+           break;
+       }
+    }
+
+    /* Success! */
+    tty_display_nhwindow(BASE_WINDOW, FALSE);
+    return;
+
+ give_up:
+    /* Quit */
+    if (selected) free((genericptr_t) selected);       /* [obsolete] */
+    bail((char *)0);
+    /*NOTREACHED*/
+    return;
 }
 
+#undef ROLE
+#undef RACE
+#undef GEND
+#undef ALGN
+
 /*
  * plname is filled either by an option (-u Player  or  -uPlayer) or
  * explicitly (by being the wizard) or by askname.
@@ -703,7 +869,7 @@ tty_askname()
     register int c, ct, tryct = 0;
 
 #ifdef SELECTSAVED
-    if (iflags.wc2_selectsaved)
+    if (iflags.wc2_selectsaved && !iflags.renameinprogress)
        switch (restore_menu(BASE_WINDOW)) {
        case -1: bail("Until next time then...");       /* quit */
                 /*NOTREACHED*/
@@ -784,6 +950,10 @@ tty_askname()
 
     /* move to next line to simulate echo of user's <return> */
     tty_curs(BASE_WINDOW, 1, wins[BASE_WINDOW]->cury + 1);
+
+    /* since we let user pick an arbitrary name now, he/she can pick
+       another one during role selection */
+    iflags.renameallowed = TRUE;
 }
 
 void