diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 5176c7ad8..5d17fd53c 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -3671,12 +3671,16 @@ applies only to new games. Enable messages about what your character hears (default on). Note that this has nothing to do with your computer's audio capabilities. Persistent. -.lp align -Your starting alignment (align:lawful, align:neutral, or align:chaotic). +.lp alignment +Your starting alignment (\f(CRalign:lawful\fP, \f(CRalign:neutral\fP, or +\f(CRalign:chaotic\fP). You may specify just the first letter. -The default is to randomly pick an appropriate alignment. -If you prefix the value with \(oq!\(cq or \(lqno\(rq, you will -exclude that alignment from being picked randomly. +Many roles and the non-human races restrict which alignments are allowed. +See +.op role +for a description of how to use negation to exclude choices. +.lp "" +Default is random. Cannot be set with the \(oqO\(cq command. Persistent. .lp autodescribe @@ -3877,14 +3881,21 @@ You should set this to something you find more appetizing than slime mold. Apples, oranges, pears, bananas, and melons already exist in NetHack, so don't use those. .lp gender -Your starting gender (gender:male or gender:female). +Your starting gender (\f(CRgender:male\fP or \f(CRgender:female\fP). You may specify just the first letter. Although you can -still denote your gender using the \(lqmale\(rq and \(lqfemale\(rq -options, the \(lqgender\(rq option will take precedence. -The default is to randomly pick an appropriate gender. -If you prefix the value with \(oq!\(cq or \(lqno\(rq, you will -exclude that gender from being picked randomly. +still denote your gender using either of the deprecated +.op male +and +.op female +options, if the +.op gender +option is also present it will take precedence. +See +.op role +for a description of how to use negation to exclude choices. +.lp "" +Default is random. Cannot be set with the \(oqO\(cq command. Persistent. .lp "goldX " @@ -4287,23 +4298,50 @@ It does not affect the clairvoyance spell where pausing to examine revealed objects or monsters is less intrusive. Default is off. Persistent. .lp "race " -Selects your race (for example, \(lqrace:human\(rq). +Selects your race (for example, \f(CRrace:human\fP). +Choices are \f(CRhuman\fP, \f(CRdwarf\fP, \f(CRelf\fP, \f(CRgnome\fP, and +\f(CRorc\fP but most roles restrict which of the non-human races are allowed. +See +.op role +for a description of how to use negation to exclude choices. +.lp "" Default is random. -If you prefix the value with \(oq!\(cq or \(lqno\(rq, you will -exclude that race from being picked randomly. Cannot be set with the \(oqO\(cq command. Persistent. .lp rest_on_space Make the space bar a synonym for the \(oq.\(cq (#wait) command (default off). Persistent. .lp "role " -Pick your type of character (for example \(lqrole:Samurai\(rq); -synonym for \(lqcharacter\(rq. -See \(lqname\(rq for an alternate method of specifying your role. -Normally only the first letter of the value is examined; \(oqr\(cq is an -exception with \(lqRogue\(rq, \(lqRanger\(rq, and \(lqrandom\(rq values. -If you prefix the value with \(oq!\(cq or \(lqno\(rq, you will -exclude that role from being picked randomly. +Pick your type of character (for example, \f(CRrole:Samurai\fP); +synonym for +.op character. +See +.op name +for an alternate method of specifying your role. +.\" Normally only the first letter of the value is examined; \(oqr\(cq is an +.\" exception with \(lqRogue\(rq, \(lqRanger\(rq, and \(lqrandom\(rq values. +.lp "" +This option can also be used to limit selection when role is chosen +randomly. +Use a space-separated list of roles and either negate each one or negate +the option itself instead. +Negation is accomplished in the same manner as with \fIboolean options\fP, +by prefixing the option or its value(s) with \(oq!\(cq or \(lqno\(rq. +.BR 0 +Examples: +.sd +.ft CR \" constant-width Roman +OPTIONS=role:!arc !bar !kni +OPTIONS=!role:arc bar kni +.ft \" revert to previous font +.ed +There can be multiple instances of the +.op role +option if they're all negations. +.\" Only one positive value is allowed, and if present, it overrides any +.\" negations. +.lp "" +Default is \f(CRrandom\fP. Cannot be set with the \(oqO\(cq command. Persistent. .lp roguesymset diff --git a/include/optlist.h b/include/optlist.h index 678878cf8..f992fa9f7 100644 --- a/include/optlist.h +++ b/include/optlist.h @@ -115,16 +115,16 @@ static int optfn_##a(int, int, boolean, char *, char *); No, Yes, No, No, NoAlias, "your character's name (e.g., name:Merlin-W)") NHOPTC(role, Advanced, PL_CSIZ, opt_in, set_gameview, - No, Yes, No, No, "character", + Yes, Yes, No, No, "character", "your starting role (e.g., Barbarian, Valkyrie)") NHOPTC(race, Advanced, PL_CSIZ, opt_in, set_gameview, - No, Yes, No, No, NoAlias, + Yes, Yes, No, No, NoAlias, "your starting race (e.g., Human, Elf)") NHOPTC(gender, Advanced, 8, opt_in, set_gameview, - No, Yes, No, No, NoAlias, + Yes, Yes, No, No, NoAlias, "your starting gender (male or female)") - NHOPTC(align, Advanced, 8, opt_in, set_gameview, - No, Yes, No, No, NoAlias, + NHOPTC(alignment, Advanced, 8, opt_in, set_gameview, + Yes, Yes, No, No, "align", "your starting alignment (lawful, neutral, or chaotic)") /* end of special ordering; remainder of entries are in alphabetical order */ diff --git a/src/options.c b/src/options.c index ee251a183..036186b32 100644 --- a/src/options.c +++ b/src/options.c @@ -575,7 +575,12 @@ check_misc_menu_command(char *opts, char *op UNUSED) */ static int -optfn_align(int optidx, int req, boolean negated, char *opts, char *op) +optfn_alignment( + int optidx, + int req, + boolean negated, + char *opts, + char *op) { if (req == do_init) { return optn_ok; @@ -7463,38 +7468,84 @@ count_menucolors(void) return count; } +/* parse 'role' or 'race' or 'gender' or 'alignment' */ static boolean -parse_role_opts(int optidx, boolean negated, const char *fullname, - char *opts, char **opp) +parse_role_opts( + int optidx, + boolean negated, + const char *fullname, + char *opts, + char **opp) { char *op = *opp; + boolean ok = FALSE; - if (negated) { - bad_negation(fullname, FALSE); - } else if ((op = string_for_env_opt(fullname, opts, FALSE)) - != empty_optstr) { - boolean val_negated = FALSE; + /* + * Accepts multiple forms + * role:priest -- play as priest + * race:!orc -- any race other than orc + * role:!cav !mon -- any role other than caveman/cavewoman or monk + * !role:tour -- any role other than tourist + * !role:tou rog wiz -- any role other than tourist or rogue or wizard + * TODO: add support for + * role:arc bar kni -- only role archeologist or barbarian or knight + * Rejected: + * role:sam !val -+ invalid; need either positive or negative subset + * !role:!sam +- not a mixture of the two and not dual negation. + */ + if ((op = string_for_env_opt(fullname, opts, FALSE)) != empty_optstr) { + char *sp; + boolean val_negated, prev_negated = FALSE, first = TRUE; - while ((*op == '!') || !strncmpi(op, "no", 2)) { - if (*op == '!') - op++; - else - op += 2; - val_negated = !val_negated; - } - if (val_negated) { - if (!setrolefilter(op)) { - config_error_add("Unknown negated parameter '%s'", op); + while (*op) { + val_negated = FALSE; + while ((*op == '!') || !strncmpi(op, "no", 2)) { + val_negated = !val_negated; + op += (*op == '!') ? 1 : (op[2] != '-') ? 2 : 3; + } + if (!*op) { + config_error_add("Negated nothing for '%s'", fullname); return FALSE; } - } else { - if (duplicate && !allopt[optidx].dupeok) - complain_about_duplicate(optidx); - *opp = op; - return TRUE; + if (!first) { + if ((val_negated ^ prev_negated) + || (negated && val_negated)) { + config_error_add("Invalid mixed negation for '%s%s'", + negated ? "!" : "", fullname); + return FALSE; + } else if (!negated && !val_negated) { + config_error_add( + "Multiple role values only allowed when list is negated"); + return FALSE; + } + } + first = FALSE; + prev_negated = val_negated; + + sp = strchr(op, ' '); + if (sp) + *sp = '\0'; + if (val_negated || negated) { + if (!setrolefilter(op)) { + config_error_add("Invalid '%s'", fullname); + return FALSE; + } + } else { + if (duplicate && !allopt[optidx].dupeok) + complain_about_duplicate(optidx); + *opp = op; + ok = TRUE; + } + + if (sp) { + *sp = ' '; + op = sp + 1; + } else { + op += strlen(op); /* break; */ + } } } - return FALSE; + return ok; } /* Check if character c is illegal as a menu command key */