]> granicus.if.org Git - nethack/commitdiff
enhanced interactive role selection (trunk only)
authornethack.rankin <nethack.rankin>
Sun, 22 Mar 2009 00:22:33 +0000 (00:22 +0000)
committernethack.rankin <nethack.rankin>
Sun, 22 Mar 2009 00:22:33 +0000 (00:22 +0000)
 [This verbose description is being committed with role.c only;
  the dozen or so other affected files will use a much shorter one.]

     Executive summary:  using Unix (or VMS) with tty, start up via
nethack -u player
and follow the prompts.  Seeing this in action will be much clearer than
any description.  It might work as is with Mac and Be too, where you
don't need to bother with "-u player" to get "who are you?".  Only half
of it will work with other ports using tty so far, and it does nothing
for ports which don't use tty.

     I started out attempting to add an option which would let you defer
picking your character name until after you'd picked role and race and
so forth (so that you could let the game pick randomly, then use your own
role- or race- or gender-specific name for the result) but threw my ends
up in frustration.  Instead, this allows you to specify race and/or gender
and/or alignment before role when interactively choosing them (ie, after
giving 'n' to the "shall I pick for you?" prompt), by adding extra menu
entries to the role menu, with similar entries in the other menus so you
can actually bounce back and forth picking whichever of the four role/race/
gender/alignment attributes in any order.  (<Someone> has a patch to
control the order of the four prompts via option setting, but it's not as
versatile as this ended up being.)

     That works pretty well, so I added a confirmation prompt when you're
done (which can be bypassed by picking new 'a' instead 'y' at the "shall
I pick your character's role [ynaq]?" prompt).  And I introduced a chance
to rename the character during that confirmation, with a quite modest
amount of spaghetti being added into main() to support it.  Picking a new
name which matches a save file will result restoring the saved game just
as if you'd used that name from the start.  [One thing which hasn't been
resolved yet is whether anything special needs to be done when changing
name to or from "wizard", particulary for ports which allow/reject wizard
mode access based on character name rather than user name.]

     Right now, renaming is only available if you've gone through the
"who are you?" prompt (thereby demonstrating that you're allowed to use
an arbitrary name), since some multi-user sites may be using scripting to
force the character name for players who share an account.  There should
be a new SYSCF option to let sites explicitly allow renaming, but this
had already grown pretty big so that is deferred.  (And I haven't yet
implemented sysconf for VMS so wouldn't have been able to test it....)

     Unfortunately, role selection has been implemented in the interface
code instead of in the core--a big mistake, in my opinion, even if some
interfaces can give easy point and click control to the player--so this
has only been implemented for tty.

     Also, renaming needs to manipulate lock files in the case where the
file name is based on the character name.  I moved the file name part
into getlock() itself, removing some clutter from main().  But getlock()
handling in pcmain.c is something I won't touch with a pole of any length.
It needs to be cleaned up before the rename capability can be activated
for ports that use that main().  The rest of the rename support there is
present but bracketed by #if 0.

     Lastly, the handling of generic character names like "player" and
"games" has been moved into plnamesuffix(), again to eliminate a bit of
clutter from main().  And plnamesuffix()'s potential for uncontrolled
recursion (if player keeps giving -Role instead of Name to askname()) has
been replaced by iteration.  It could still go on forever if the player
is persistent or askname() goes haywire, but I don't think we really care.

     The interface-specific changes are limited to player_selection() and
askname(), so folks can add this to other interfaces as desired without
flailing all over the place.  But the changes to tty's player_selection()
were quite extensive.  (Fortunately, some of it is just fixing up the
indentation to match changes in block nesting.)  Like tty, win32 and gem
use build_plselection_prompt().  It now returns a string ending in "[ynaq]"
rather than just "[ynq]" so if they don't bother with these changes, they
should either fix that up or at least accept 'a' as a synonym for 'y'
during the initial "shall I pick your character's role?" prompt.

src/role.c

index 512acef36032c13a22bd1037757ec2c7a5615157..021e23a36d70dac022210b24e70f5cccc5ca67f8 100644 (file)
@@ -1270,10 +1270,10 @@ build_plselection_prompt(buf, buflen, rolenum, racenum, gendnum, alignnum)
 char *buf;
 int buflen, rolenum, racenum, gendnum, alignnum;
 {
-       const char *defprompt = "Shall I pick a character for you? [ynq] ";
+       const char *defprompt = "Shall I pick a character for you? [ynaq] ";
        int num_post_attribs = 0;
        char tmpbuf[BUFSZ];
-       
+
        if (buflen < QBUFSZ)
                return (char *)defprompt;
 
@@ -1315,7 +1315,7 @@ int buflen, rolenum, racenum, gendnum, alignnum;
                        Strcat(buf, "alignment");
                }
        }
-       Strcat(buf, " for you? [ynq] ");
+       Strcat(buf, " for you? [ynaq] ");
        return buf;
 }
 
@@ -1328,8 +1328,18 @@ int buflen, rolenum, racenum, gendnum, alignnum;
 void
 plnamesuffix()
 {
-       char *sptr, *eptr;
-       int i;
+    char *sptr, *eptr;
+    int i;
+
+    /* some generic user names will be ignored in favor of prompting */
+    i = (int)strlen(plname);
+    if ((i >= 4 && !strncmpi(plname, "player", i)) ||      /* play[er] */
+           (i >= 4 && !strncmpi(plname, "games", i)) ||           /* game[s] */
+           (i >= 7 && !strncmpi(plname, "nethacker", i)))  /* nethack[er] */
+       *plname = '\0'; /* call askname() */
+
+    do {
+       if (!*plname) askname();        /* fill plname[] if necessary */
 
        /* Look for tokens delimited by '-' */
        if ((eptr = index(plname, '-')) != (char *) 0)
@@ -1350,17 +1360,222 @@ plnamesuffix()
            else if ((i = str2align(sptr)) != ROLE_NONE)
                flags.initalign = i;
        }
-       if(!plname[0]) {
-           askname();
-           plnamesuffix();
-       }
+    } while (!*plname);
 
-       /* commas in the plname confuse the record file, convert to spaces */
-       for (sptr = plname; *sptr; sptr++) {
-               if (*sptr == ',') *sptr = ' ';
-       }
+    /* commas in the plname confuse the record file, convert to spaces */
+    for (sptr = plname; *sptr; sptr++) {
+       if (*sptr == ',') *sptr = ' ';
+    }
 }
 
+void
+role_selection_prolog(which, where)
+int which;
+winid where;
+{
+    static const char NEARDATA
+       choosing[] = " choosing now",
+       not_yet[] = " not yet specified",
+       rand_choice[] = " random";
+    char buf[BUFSZ];
+    int r, c, g, a, allowmask;
+
+    switch (which) {
+    case RS_NAME:
+       if (*plname) return;
+       break;
+    case RS_ROLE:
+       if (flags.initrole != ROLE_NONE && flags.initrole != ROLE_RANDOM)
+           return;
+       break;
+    case RS_RACE:
+       if (flags.initrace != ROLE_NONE && flags.initrace != ROLE_RANDOM)
+           return;
+       break;
+    case RS_GENDER:
+       if (flags.initgend != ROLE_NONE && flags.initgend != ROLE_RANDOM)
+           return;
+       break;
+    case RS_ALGNMNT:
+       if (flags.initalign != ROLE_NONE && flags.initalign != ROLE_RANDOM)
+           return;
+       break;
+    }
+
+    r = flags.initrole;
+    c = flags.initrace;
+    g = flags.initgend;
+    a = flags.initalign;
+    if (r >= 0) {
+       allowmask = roles[r].allow;
+       if ((allowmask & ROLE_RACEMASK) == MH_HUMAN)
+           c = 0;      /* races[human] */
+       else if (c >= 0 && !(allowmask & ROLE_RACEMASK & races[c].allow))
+           c = ROLE_RANDOM;
+       if ((allowmask & ROLE_GENDMASK) == ROLE_MALE)
+           g = 0;      /* role forces male (hypothetical) */
+       else if ((allowmask & ROLE_GENDMASK) == ROLE_FEMALE)
+           g = 1;      /* role forces female (valkyrie) */
+       if ((allowmask & ROLE_ALIGNMASK) == AM_LAWFUL)
+           a = 0;      /* aligns[lawful] */
+       else if ((allowmask & ROLE_ALIGNMASK) == AM_NEUTRAL)
+           a = 1;      /* aligns[neutral] */
+       else if ((allowmask & ROLE_ALIGNMASK) == AM_CHAOTIC)
+           a = 2;      /* alings[chaotic] */
+    }
+    if (c >= 0) {
+       allowmask = races[c].allow;
+       if ((allowmask & ROLE_ALIGNMASK) == AM_LAWFUL)
+           a = 0;      /* aligns[lawful] */
+       else if ((allowmask & ROLE_ALIGNMASK) == AM_NEUTRAL)
+           a = 1;      /* aligns[neutral] */
+       else if ((allowmask & ROLE_ALIGNMASK) == AM_CHAOTIC)
+           a = 2;      /* alings[chaotic] */
+       /* [c never forces gender] */
+    }
+    /* [g and a don't constrain anything sufficiently
+       to narrow something done to a single choice] */
+
+    Sprintf(buf, "%12s ", "name:");
+    Strcat(buf, (which == RS_NAME) ? choosing : !*plname ? not_yet : plname);
+    putstr(where, 0, buf);
+    Sprintf(buf, "%12s ", "role:");
+    Strcat(buf, (which == RS_ROLE) ? choosing : (r == ROLE_NONE) ? not_yet :
+               (r == ROLE_RANDOM) ? rand_choice : roles[r].name.m);
+    if (r >= 0 && roles[r].name.f) {
+       /* distinct female name [caveman/cavewoman, priest/priestess] */
+       if (g == 1)
+           /* female specified; replace male role name with female one */
+           Sprintf(index(buf, ':'), ": %s", roles[r].name.f);
+       else if (g < 0)
+           /* gender unspecified; append slash and female role name */
+           Sprintf(eos(buf), "/%s", roles[r].name.f);
+    }
+    putstr(where, 0, buf);
+    Sprintf(buf, "%12s ", "race:");
+    Strcat(buf, (which == RS_RACE) ? choosing : (c == ROLE_NONE) ? not_yet :
+               (c == ROLE_RANDOM) ? rand_choice : races[c].noun);
+    putstr(where, 0, buf);
+    Sprintf(buf, "%12s ", "gender:");
+    Strcat(buf, (which == RS_GENDER) ? choosing : (g == ROLE_NONE) ? not_yet :
+               (g == ROLE_RANDOM) ? rand_choice : genders[g].adj);
+    putstr(where, 0, buf);
+    Sprintf(buf, "%12s ", "alignment:");
+    Strcat(buf, (which == RS_ALGNMNT) ? choosing : (a == ROLE_NONE) ? not_yet :
+               (a == ROLE_RANDOM) ? rand_choice : aligns[a].adj);
+    putstr(where, 0, buf);
+}
+
+void
+role_menu_extra(which, where)
+int which;
+winid where;
+{
+    static NEARDATA const char RS_menu_let[] = {
+       '=',            /* name */
+       '?',            /* role */
+       '/',            /* race */
+       '\"',           /* gender */
+       '[',            /* alignment */
+    };
+    anything any;
+    char buf[BUFSZ];
+    const char *what = 0, *constrainer = 0, *forcedvalue = 0;
+    int f = 0, r, c, g, a, allowmask;
+
+    r = flags.initrole;
+    c = flags.initrace;
+    switch (which) {
+    case RS_NAME:
+       what = "name";
+       break;
+    case RS_ROLE:
+       what = "role";
+       f = r;
+       /* nothing contrains role to a single choice */
+       break;
+    case RS_RACE:
+       what = "race";
+       f = flags.initrace;
+       c = ROLE_NONE;          /* override player's setting */
+       if (r >= 0) {
+           allowmask = roles[r].allow & ROLE_RACEMASK;
+           if (allowmask == MH_HUMAN)
+               c = 0;          /* races[human] */
+           if (c >= 0) {
+               constrainer = "role";
+               forcedvalue = races[c].noun;
+           }
+       }
+       break;
+    case RS_GENDER:
+       what = "gender";
+       f = flags.initgend;
+       g = ROLE_NONE;
+       if (r >= 0) {
+           allowmask = roles[r].allow & ROLE_GENDMASK;
+           if (allowmask == ROLE_MALE)
+               g = 0;          /* genders[male] */
+           else if (allowmask == ROLE_FEMALE)
+               g = 1;          /* genders[female] */
+           if (g >= 0) {
+               constrainer = "role";
+               forcedvalue = genders[g].adj;
+           }
+       }
+       break;
+    case RS_ALGNMNT:
+       what = "alignment";
+       f = flags.initalign;
+       a = ROLE_NONE;
+       if (r >= 0) {
+           allowmask = roles[r].allow & ROLE_ALIGNMASK;
+           if (allowmask == AM_LAWFUL)
+               a = 0;          /* aligns[lawful] */
+           else if (allowmask == AM_NEUTRAL)
+               a = 1;          /* aligns[neutral] */
+           else if (allowmask == AM_CHAOTIC)
+               a = 2;          /* aligns[chaotic] */
+           if (a >= 0) constrainer = "role";
+       }
+       if (c >= 0 && !constrainer) {
+           allowmask = races[c].allow & ROLE_ALIGNMASK;
+           if (allowmask == AM_LAWFUL)
+               a = 0;          /* aligns[lawful] */
+           else if (allowmask == AM_NEUTRAL)
+               a = 1;          /* aligns[neutral] */
+           else if (allowmask == AM_CHAOTIC)
+               a = 2;          /* aligns[chaotic] */
+           if (a >= 0) constrainer = "race";
+       }
+       if (a >= 0) forcedvalue = aligns[a].adj;
+       break;
+    }
+
+    any = zeroany;     /* zero out all bits */
+    if (constrainer) {
+       any.a_int = 0;
+       /* use four spaces of padding to fake a grayed out menu choice */
+       Sprintf(buf, "%4s%s forces %s", "", constrainer, forcedvalue);
+       add_menu(where, NO_GLYPH, &any, ' ',
+                0, ATR_NONE, buf, MENU_UNSELECTED);
+    } else if (what) {
+       any.a_int = RS_menu_arg(which);
+       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 == ROLE_RANDOM) {
+       any.a_int = ROLE_RANDOM;
+       add_menu(where, NO_GLYPH, &any, '*',
+                0, ATR_NONE, "Random", MENU_UNSELECTED);
+    } else if (which == ROLE_NONE) {
+       any.a_int = ROLE_NONE;
+       add_menu(where, NO_GLYPH, &any, 'q',
+                0, ATR_NONE, "Quit", MENU_UNSELECTED);
+    } else {
+       impossible("role_menu_extra: bad arg (%s)", which);
+    }
+}
 
 /*
  *     Special setup modifications here: