diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index d4d6a1971..b54faf82d 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -656,7 +656,8 @@ list of all discovered objects) and the \(oq\f(CR\`\fP\(cq (knownclass, show a list of discovered objects in a particular class) commands to offer a menu of several sorting alternatives (which sets a new value for the .op sortdiscoveries -option); also for \(lq#vanquished\(rq command to offer a sorting menu. +option); also for \(lq#vanquished\(rq and \(lq#genocided\(rq commands +to offer a sorting menu. .lp "" A few other commands (eat food, offer sacrifice, apply tinning-kit, drink/quaff, dip, tip container) use @@ -1299,7 +1300,19 @@ Force a lock. Autocompletes. Default key is \(oqM-f\(cq. .lp #genocided -List any monster types which have been genocided or become extinct. +List any monster types which have been genocided. +In explore mode and debug mode it also shows types which have become +extinct. +.lp "" +The display order is the same as is used by #vanquished. +The \(oqm\(cq prefix brings up a menu of available sorting orders, and +doing that for either #genocided or #vanquished changes the order for both. +.lp "" +If the sorting order is \(lqcount high to low\(rq or \(lqcount low to high\(rq +(which are applicable for #vanquished), that will be ignored for #genocided +and alphabetical will be used instead. +The menu omits those two choices when used for #genocide. +.lp "" Autocompletes. Default key is \(oqM-g\(cq. .lp "#glance " @@ -1715,6 +1728,8 @@ list contains at least two types of monsters). Whichever ordering is picked gets assigned to the .op sortvanquished option so is remembered for subsequent #vanquished requests. +The \(lq#genocided\(rq command shares this sorting order. +.lp "" During end-of-game disclosure, when asked whether to show vanquished monsters answering \(oq\f(CRa\fP\(cq will let you choose from the sort menu. .lp "" @@ -3856,8 +3871,8 @@ lets you refine how it behaves. Here are the valid prefixes: .CC \- "do not disclose it and do not prompt." .ei .ed -The listing of vanquished monsters can be sorted, -so there are two additional choices for \(oqv\(cq: +The listings of vanquished monsters and of genocided types can be sorted, +so there are two additional choices for \(oqv\(cq and \(oqg\(cq: .sd .si .CC ? "prompt you and default to ask on the prompt;" @@ -3867,8 +3882,9 @@ so there are two additional choices for \(oqv\(cq: Asking refers to picking one of the orderings from a menu. The \(oq+\(cq disclose without prompting choice, or being prompted and answering \(oqy\(cq rather than \(oqa\(cq, -will default to showing monsters in the traditional order, -from high level to low level. +will default to showing monsters in the order specified by the +.op sortvanquished +option. .lp "" Omitted categories are implicitly added with \(oqn\(cq prefix. Specified categories with omitted prefix implicitly use \(oq+\(cq prefix. @@ -4490,7 +4506,8 @@ default. Sort the pack contents by type when displaying inventory (default on). Persistent. .lp sortvanquished -Controls the sorting behavior for the output of the #vanquished command. +Controls the sorting behavior for the output of the #vanquished command +and also for the #genocided command. Persistent. .lp "" The possible values are: @@ -4516,7 +4533,8 @@ order by count, high to low; ties are broken by internal monster index; order by count, low to high; ties broken by internal index. .PE Can be interactively set via the \(oq\f(CRm O\fP\(cq command or via using -the \(oq\f(CRm\fP\(cq prefix before the #vanquished command. +the \(oq\f(CRm\fP\(cq prefix before either the #vanquished command +or the #genocided command. .lp "sounds " Allow sounds to be emitted from an integrated sound library (default on). .lp "sparkle " diff --git a/include/extern.h b/include/extern.h index a41909a6a..9edc23d9b 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1109,7 +1109,7 @@ extern schar achieve_rank(int); extern boolean sokoban_in_play(void); extern int do_gamelog(void); extern void show_gamelog(int); -extern int set_vanq_order(void); +extern int set_vanq_order(boolean); extern int dovanquished(void); extern int doborn(void); extern void list_vanquished(char, boolean); diff --git a/src/cmd.c b/src/cmd.c index 771ac9b7f..186a568ff 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -581,7 +581,8 @@ int doextlist(void) { register const struct ext_func_tab *efp = (struct ext_func_tab *) 0; - char buf[BUFSZ], searchbuf[BUFSZ], promptbuf[QBUFSZ]; + char buf[BUFSZ], searchbuf[BUFSZ], descbuf[BUFSZ], promptbuf[QBUFSZ]; + const char *cmd_desc; winid menuwin; anything any; menu_item *selected; @@ -654,21 +655,11 @@ doextlist(void) for (efp = extcmdlist; efp->ef_txt; efp++) { int wizc; - if ((efp->flags & (CMD_NOT_AVAILABLE|INTERNALCMD)) != 0) + if ((efp->flags & (CMD_NOT_AVAILABLE | INTERNALCMD)) != 0) continue; /* if hiding non-autocomplete commands, skip such */ if (menumode == 1 && (efp->flags & AUTOCOMPLETE) == 0) continue; - /* if searching, skip this command if it doesn't match */ - if (*searchbuf - /* first try case-insensitive substring match */ - && !strstri(efp->ef_txt, searchbuf) - && !strstri(efp->ef_desc, searchbuf) - /* wildcard support; most interfaces use case-insensitive - pmatch rather than regexp for menu searching */ - && !pmatchi(searchbuf, efp->ef_txt) - && !pmatchi(searchbuf, efp->ef_desc)) - continue; /* skip wizard mode commands if not in wizard mode; when showing two sections, skip wizard mode commands in pass==0 and skip other commands in pass==1 */ @@ -677,6 +668,26 @@ doextlist(void) continue; if (!onelist && pass != wizc) continue; + /* command descripton might get modified on the fly */ + cmd_desc = efp->ef_desc; + /* suppress part of the descripton for #genocided if it + doesn't apply during the current game */ + if (!wizard && !discover + && (efp->flags & GENERALCMD) != 0 /* minor optimization */ + && strstri(cmd_desc, "extinct")) + cmd_desc = strsubst(strcpy(descbuf, cmd_desc), + " been genocided or become extinct", + " been genocided"); + /* if searching, skip this command if it doesn't match */ + if (*searchbuf + /* first try case-insensitive substring match */ + && !strstri(efp->ef_txt, searchbuf) + && !strstri(cmd_desc, searchbuf) + /* wildcard support; most interfaces use case-insensitive + pmatch rather than regexp for menu searching */ + && !pmatchi(searchbuf, efp->ef_txt) + && !pmatchi(searchbuf, cmd_desc)) + continue; /* We're about to show an item, have we shown the menu yet? Doing menu in inner loop like this on demand avoids a @@ -692,7 +703,7 @@ doextlist(void) /* longest ef_txt at present is "wizrumorcheck" (13 chars); 2nd field will be " " or " [A]" or " [m]" or "[mA]" */ Sprintf(buf, " %-14s %4s %s", efp->ef_txt, - doc_extcmd_flagstr(menuwin, efp), efp->ef_desc); + doc_extcmd_flagstr(menuwin, efp), cmd_desc); add_menu(menuwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE); ++n; @@ -2581,11 +2592,12 @@ struct ext_func_tab extcmdlist[] = { dofire, 0, NULL }, { M('f'), "force", "force a lock", doforce, AUTOCOMPLETE, NULL }, - { ';', "glance", "show what type of thing a map symbol corresponds to", - doquickwhatis, IFBURIED | GENERALCMD, NULL }, { M('g'), "genocided", "list monsters that have been genocided or become extinct", - dogenocided, IFBURIED | AUTOCOMPLETE, NULL }, + dogenocided, + IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, NULL }, + { ';', "glance", "show what type of thing a map symbol corresponds to", + doquickwhatis, IFBURIED | GENERALCMD, NULL }, { '?', "help", "give a help message", dohelp, IFBURIED | GENERALCMD, NULL }, { '\0', "herecmdmenu", "show menu of commands you can do here", @@ -2768,7 +2780,8 @@ struct ext_func_tab extcmdlist[] = { /* (see comment for dodown() above */ doup, CMD_M_PREFIX, NULL }, { M('V'), "vanquished", "list vanquished monsters", - dovanquished, IFBURIED | AUTOCOMPLETE | CMD_M_PREFIX, NULL }, + dovanquished, + IFBURIED | AUTOCOMPLETE | GENERALCMD | CMD_M_PREFIX, NULL }, { M('v'), "version", "list compile time options for this version of NetHack", doextversion, IFBURIED | AUTOCOMPLETE | GENERALCMD, NULL }, diff --git a/src/insight.c b/src/insight.c index 456d5e095..6e38f6760 100644 --- a/src/insight.c +++ b/src/insight.c @@ -33,6 +33,7 @@ static void attributes_enlightenment(int, int); static void show_achievements(int); static int QSORTCALLBACK vanqsort_cmp(const genericptr, const genericptr); static int num_extinct(void); +static int num_gone(int, int *); extern const char *const hu_stat[]; /* hunger status from eat.c */ extern const char *const enc_stat[]; /* encumbrance status from botl.c */ @@ -2613,11 +2614,13 @@ vanqsort_cmp( /* returns -1 if cancelled via ESC */ int -set_vanq_order(void) +set_vanq_order(boolean for_vanq) { winid tmpwin; menu_item *selected; anything any; + char buf[BUFSZ]; + const char *desc; int i, n, choice, clr = 0; @@ -2627,13 +2630,25 @@ set_vanq_order(void) for (i = 0; i < SIZE(vanqorders); i++) { if (i == VANQ_ALPHA_MIX || i == VANQ_MCLS_HTOL) /* skip these */ continue; + /* suppress some orderings if this menu if for 'm #genocided' */ + if (!for_vanq && (i == VANQ_COUNT_H_L || i == VANQ_COUNT_L_H)) + continue; + desc = vanqorders[i][2]; + /* unique monsters can't be genocided so "alpha, unique separate" + and "alpha, unique intermixed" are confusing descriptions when + this menu is for #genocided rather than for #vanquished */ + if (!for_vanq && i == VANQ_ALPHA_SEP) + desc = "alphabetically"; any.a_int = i + 1; add_menu(tmpwin, &nul_glyphinfo, &any, *vanqorders[i][0], 0, ATR_NONE, clr, vanqorders[i][2], (i == flags.vanq_sortmode) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); } - end_menu(tmpwin, "Sort order for vanquished monster counts"); + Sprintf(buf, "Sort order for %s", + for_vanq ? "vanquished monster counts (also genocided types)" + : "genocided monster types (also vanquished counts)"); + end_menu(tmpwin, buf); n = select_menu(tmpwin, PICK_ONE, &selected); destroy_nhwindow(tmpwin); @@ -2656,48 +2671,9 @@ dovanquished(void) return ECMD_OK; } -DISABLE_WARNING_FORMAT_NONLITERAL - -/* #wizborn extended command */ -int -doborn(void) -{ - static const char fmt[] = "%4i %4i %c %-30s"; - int i; - winid datawin = create_nhwindow(NHW_TEXT); - char buf[BUFSZ]; - int nborn = 0, ndied = 0; - - putstr(datawin, 0, "died born"); - for (i = LOW_PM; i < NUMMONS; i++) - if (gm.mvitals[i].born || gm.mvitals[i].died - || (gm.mvitals[i].mvflags & G_GONE) != 0) { - Sprintf(buf, fmt, - gm.mvitals[i].died, gm.mvitals[i].born, - ((gm.mvitals[i].mvflags & G_GONE) == G_EXTINCT) ? 'E' - : ((gm.mvitals[i].mvflags & G_GONE) == G_GENOD) ? 'G' - : ((gm.mvitals[i].mvflags & G_GONE) != 0) ? 'X' - : ' ', - mons[i].pmnames[NEUTRAL]); - putstr(datawin, 0, buf); - nborn += gm.mvitals[i].born; - ndied += gm.mvitals[i].died; - } - - putstr(datawin, 0, ""); - Sprintf(buf, fmt, ndied, nborn, ' ', ""); - - display_nhwindow(datawin, FALSE); - destroy_nhwindow(datawin); - - return ECMD_OK; -} - -RESTORE_WARNING_FORMAT_NONLITERAL - /* high priests aren't unique but are flagged as such to simplify something */ -#define UniqCritterIndx(mndx) ((mons[mndx].geno & G_UNIQ) \ - && mndx != PM_HIGH_CLERIC) +#define UniqCritterIndx(mndx) \ + ((mons[mndx].geno & G_UNIQ) != 0 && mndx != PM_HIGH_CLERIC) #define done_stopprint gp.program_state.stopprint @@ -2745,7 +2721,7 @@ list_vanquished(char defquery, boolean ask) if (c == 'a' && ntypes > 1) { /* ask player to choose sort order */ /* choose value for vanq_sortmode via menu; ESC cancels list of vanquished monsters but does not set 'done_stopprint' */ - if (set_vanq_order() < 0) + if (set_vanq_order(TRUE) < 0) return; } uniq_header = (flags.vanq_sortmode == VANQ_ALPHA_SEP); @@ -2807,7 +2783,7 @@ list_vanquished(char defquery, boolean ask) : !digit(buf[2]) ? 4 : 0; if (class_header) ++pfx; - Snprintf(buftoo, sizeof(buftoo), "%*s%s", pfx, "", buf); + Snprintf(buftoo, sizeof buftoo, "%*s%s", pfx, "", buf); putstr(klwin, 0, buftoo); } /* @@ -2874,24 +2850,52 @@ num_extinct(void) return n; } +/* collect both genocides and extintctions, skipping uniques */ +static int +num_gone(int mvflags, int *mindx) +{ + uchar mflg = (uchar) mvflags; + int i, n = 0; + + (void) memset((genericptr_t) mindx, 0, NUMMONS * sizeof *mindx); + + for (i = LOW_PM; i < NUMMONS; ++i) { + /* uniques can't be genocided but can become extinct; + however, they're never reported as extinct, so skip them */ + if (UniqCritterIndx(i)) + continue; + + if ((gm.mvitals[i].mvflags & mflg) != 0) + mindx[n++] = i; + } + return n; +} + /* show genocided and extinct monster types for final disclosure/dumplog or for the #genocided command */ void list_genocided(char defquery, boolean ask) { - register int i; - int ngenocided, nextinct; + register int i, mndx; + int ngenocided, nextinct, ngone, mvflags, mindx[NUMMONS]; char c; winid klwin; char buf[BUFSZ]; boolean dumping; /* for DUMPLOG; doesn't need to be conditional */ + boolean both = (gp.program_state.gameover || wizard || discover); dumping = (defquery == 'd'); if (dumping) defquery = 'y'; + /* this goess through the whole monster list up to three times but will + happen rarely and is simpler than a more general single pass check; + extinctions are only revealed during end of game disclosure or when + running in wizard or explore mode */ ngenocided = num_genocides(); - nextinct = num_extinct(); + nextinct = both ? num_extinct() : 0; + mvflags = G_GENOD | (both ? G_EXTINCT : 0); + ngone = num_gone(mvflags, mindx); /* genocided or extinct species list */ if (ngenocided != 0 || nextinct != 0) { @@ -2899,10 +2903,36 @@ list_genocided(char defquery, boolean ask) (nextinct && !ngenocided) ? "extinct " : "", (ngenocided) ? " genocided" : "", (nextinct && ngenocided) ? " and extinct" : ""); - c = ask ? yn_function(buf, ynqchars, defquery, TRUE) : defquery; + c = ask ? yn_function(buf, ynaqchars, defquery, TRUE) : defquery; if (c == 'q') done_stopprint++; - if (c == 'y') { + if (c == 'y' || c == 'a') { + int save_sortmode; + char mlet, prev_mlet = 0; + boolean class_header = FALSE; + + if (ngone > 1) { + if (c == 'a') { /* ask player to choose sort order */ + /* #genocided shares #vanquished's sort order */ + if (set_vanq_order(FALSE) < 0) + return; + } + /* sort orderings count-high-to-low or count-low-to-high + don't make sense for genocides; if the preferred order + to set to either of those, use alphabetical instead; + note: the tie breaker for by-class is level-high-to-low + or level-low-to-high rather than count so is ok as-is */ + save_sortmode = flags.vanq_sortmode; + if (flags.vanq_sortmode == VANQ_COUNT_H_L + || flags.vanq_sortmode == VANQ_COUNT_L_H) + flags.vanq_sortmode = VANQ_ALPHA_MIX; + qsort((genericptr_t) mindx, ngone, + sizeof *mindx, vanqsort_cmp); + class_header = (flags.vanq_sortmode == VANQ_MCLS_LTOH + || flags.vanq_sortmode == VANQ_MCLS_HTOL); + flags.vanq_sortmode = save_sortmode; + } + klwin = create_nhwindow(NHW_MENU); Sprintf(buf, "%s%s species:", (ngenocided) ? "Genocided" : "Extinct", @@ -2911,23 +2941,29 @@ list_genocided(char defquery, boolean ask) if (!dumping) putstr(klwin, 0, ""); - for (i = LOW_PM; i < NUMMONS; i++) { - /* uniques can't be genocided but can become extinct; - however, they're never reported as extinct, so skip them */ - if (UniqCritterIndx(i)) - continue; - if (gm.mvitals[i].mvflags & G_GONE) { - Sprintf(buf, " %s", makeplural(mons[i].pmnames[NEUTRAL])); - /* - * "Extinct" is unfortunate terminology. A species - * is marked extinct when its birth limit is reached, - * but there might be members of the species still - * alive, contradicting the meaning of the word. - */ - if ((gm.mvitals[i].mvflags & G_GONE) == G_EXTINCT) - Strcat(buf, " (extinct)"); - putstr(klwin, 0, buf); + for (i = 0; i < ngone; ++i) { + mndx = mindx[i]; + mlet = mons[mndx].mlet; + if (class_header && mlet != prev_mlet) { + Strcpy(buf, def_monsyms[(int) mlet].explain); + putstr(klwin, ask ? 0 : iflags.menu_headings, + upstart(buf)); + prev_mlet = mlet; } + Sprintf(buf, " %s", makeplural(mons[mndx].pmnames[NEUTRAL])); + /* + * "Extinct" is unfortunate terminology. A species + * is marked extinct when its birth limit is reached, + * but there might be members of the species still + * alive, contradicting the meaning of the word. + * + * We only append "(extinct)" if the G_GENOD bit is + * clear. During normal play, 'mndx' won't be in the + * collected list unless that bit is set. + */ + if ((gm.mvitals[mndx].mvflags & G_GONE) == G_EXTINCT) + Strcat(buf, " (extinct)"); + putstr(klwin, 0, buf); } if (!dumping) putstr(klwin, 0, ""); @@ -2946,10 +2982,11 @@ list_genocided(char defquery, boolean ask) /* See the comment for similar code near the end of list_vanquished(). */ } else if (!gp.program_state.gameover) { - /* #genocided rather than final disclosure, so pline() is ok */ - pline("No creatures have been genocided or become extinct."); + /* #genocided rather than final disclosure, so pline() is ok and + extinction has been ignored */ + pline("No creatures have been genocided."); #ifdef DUMPLOG - } else if (dumping) { + } else if (dumping) { /* 'gameover' is True if we make it here */ putstr(0, 0, "No species were genocided or became extinct."); #endif } @@ -2959,10 +2996,49 @@ list_genocided(char defquery, boolean ask) int dogenocided(void) { - list_genocided('y', FALSE); + list_genocided(iflags.menu_requested ? 'a' : 'y', FALSE); return ECMD_OK; } +DISABLE_WARNING_FORMAT_NONLITERAL + +/* #wizborn extended command */ +int +doborn(void) +{ + static const char fmt[] = "%4i %4i %c %-30s"; + int i; + winid datawin = create_nhwindow(NHW_TEXT); + char buf[BUFSZ]; + int nborn = 0, ndied = 0; + + putstr(datawin, 0, "died born"); + for (i = LOW_PM; i < NUMMONS; i++) + if (gm.mvitals[i].born || gm.mvitals[i].died + || (gm.mvitals[i].mvflags & G_GONE) != 0) { + Sprintf(buf, fmt, + gm.mvitals[i].died, gm.mvitals[i].born, + ((gm.mvitals[i].mvflags & G_GONE) == G_EXTINCT) ? 'E' + : ((gm.mvitals[i].mvflags & G_GONE) == G_GENOD) ? 'G' + : ((gm.mvitals[i].mvflags & G_GONE) != 0) ? 'X' + : ' ', + mons[i].pmnames[NEUTRAL]); + putstr(datawin, 0, buf); + nborn += gm.mvitals[i].born; + ndied += gm.mvitals[i].died; + } + + putstr(datawin, 0, ""); + Sprintf(buf, fmt, ndied, nborn, ' ', ""); + + display_nhwindow(datawin, FALSE); + destroy_nhwindow(datawin); + + return ECMD_OK; +} + +RESTORE_WARNING_FORMAT_NONLITERAL + /* * align_str(), piousness(), mstatusline() and ustatusline() once resided * in pline.c, then got moved to priest.c just to be out of there. They @@ -3233,5 +3309,10 @@ ustatusline(void) #undef you_have_been #undef you_have_never #undef you_have_X +#undef LL_majors +#undef majorevent +#undef spoilerevent +#undef UniqCritterIndx +#undef done_stopprint /*insight.c*/ diff --git a/src/options.c b/src/options.c index 87dc6ac09..4be94833d 100644 --- a/src/options.c +++ b/src/options.c @@ -1313,7 +1313,7 @@ optfn_disclose(int optidx, int req, boolean negated, char *opts, char *op) * DISCLOSE_PROMPT_DEFAULT_NO ask with default answer no * DISCLOSE_YES_WITHOUT_PROMPT always disclose and don't ask * DISCLOSE_NO_WITHOUT_PROMPT never disclose and don't ask - * DISCLOSE_PROMPT_DEFAULT_SPECIAL for 'vanquished' only... + * DISCLOSE_PROMPT_DEFAULT_SPECIAL for 'vanq'/'genod' only... * DISCLOSE_SPECIAL_WITHOUT_PROMPT ...to set up sort order. * * Those setting values can be used in the option @@ -1367,7 +1367,7 @@ optfn_disclose(int optidx, int req, boolean negated, char *opts, char *op) continue; } if (prefix_val != -1) { - if (*dop != 'v') { + if (*dop != 'v' && *dop != 'g') { if (prefix_val == DISCLOSE_PROMPT_DEFAULT_SPECIAL) prefix_val = DISCLOSE_PROMPT_DEFAULT_YES; if (prefix_val == DISCLOSE_SPECIAL_WITHOUT_PROMPT) @@ -3598,7 +3598,7 @@ optfn_sortvanquished( uchar prev_sortmode = flags.vanq_sortmode; /* return handler_sortvanquished(); */ - (void) set_vanq_order(); /* insight.c */ + (void) set_vanq_order(TRUE); /* insight.c */ pline("'%s' %s \"%s: %s\".", optname, (flags.vanq_sortmode == prev_sortmode) ? "not changed, still" @@ -5287,7 +5287,7 @@ handler_disclose(void) "Always disclose, without prompting", (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); - if (*disclosure_names[i] == 'v') { + if (*disclosure_names[i] == 'v' || *disclosure_names[i] == 'g') { any.a_char = DISCLOSE_SPECIAL_WITHOUT_PROMPT; /* '#' */ add_menu(tmpwin, &nul_glyphinfo, &any, 0, any.a_char, ATR_NONE, clr, @@ -5307,11 +5307,11 @@ handler_disclose(void) "Prompt, with default answer of \"Yes\"", (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); - if (*disclosure_names[i] == 'v') { + if (*disclosure_names[i] == 'v' || *disclosure_names[i] == 'g') { any.a_char = DISCLOSE_PROMPT_DEFAULT_SPECIAL; /* '?' */ add_menu(tmpwin, &nul_glyphinfo, &any, 0, any.a_char, ATR_NONE, clr, - "Prompt, with default answer of \"Ask\" to request sort menu", + "Prompt, with default answer of \"Ask\" to request sort menu", (c == any.a_char) ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE); }