From: nethack.rankin Date: Sun, 22 Mar 2009 00:22:33 +0000 (+0000) Subject: enhanced interactive role selection (trunk only) X-Git-Tag: MOVE2GIT~388 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c35d7f42bee2d88737f4b2f930c890d2aab2ab6c;p=nethack enhanced interactive role selection (trunk only) [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. ( 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. --- diff --git a/src/role.c b/src/role.c index 512acef36..021e23a36 100644 --- a/src/role.c +++ b/src/role.c @@ -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: