From 96f5f032871162f30c4d8e29dabc75eccd1d2ec9 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 14 Dec 2022 13:14:54 -0800 Subject: [PATCH] revise the role, race, gender, align options Using role:!wizard to limit which roles would be candidates for random selection didn't work as I expected. It required a separate option setting for role to exclude. This implements how I thought it worked: |OPTIONS=role:!ranger !samurai !wizard will exclude multiple roles with a space-separated list in a single option setting. It also adds support for |OPTIONS=!role:ranger samurai wizard to do the same thing. (OPTIONS=!role:!ranger isn't allowed.) I thought 'OPTIONS=role:barbarian caveman knight' could be used to limit random selection to those choices, but that doesn't work and I haven't attempted to implement it. This also renames the 'align' option to 'alignment'. That made the truncation to 'align' become ambiguous, so it got added back as an alias for the full name. Guidebook.tex is lagging; I'm burned out. --- doc/Guidebook.mn | 80 ++++++++++++++++++++++++++++---------- include/optlist.h | 10 ++--- src/options.c | 99 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 139 insertions(+), 50 deletions(-) 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 */