diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index bcda940cf..e8f7323b4 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1534,6 +1534,7 @@ curses: if a menu of objects contains at least one iron ball, and player is not already in the midst of entering a count, recognize '0' as a group accelerator rather than the start of a count curses: obey timed_delay option +curses: support tty-style extended role/race/&c selection at start of new game macOS: Xcode project was failing to build if the path to the NetHack source tree contained a space; the issue was within some shell script code contained within the project diff --git a/include/decl.h b/include/decl.h index 4682ca49d..3a24c8370 100644 --- a/include/decl.h +++ b/include/decl.h @@ -101,6 +101,7 @@ struct sinfo { int in_self_recover; /* processsing orphaned level files */ int in_checkpoint; /* saving insurance checkpoint */ int in_parseoptions; /* in parseoptions */ + int in_role_selection; /* role/race/&c selection menus in progress */ int config_error_ready; /* config_error_add is ready, available */ int beyond_savefile_load; /* set when past savefile loading */ #ifdef PANICLOG diff --git a/include/extern.h b/include/extern.h index 3e446f7a6..795ac92ad 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2418,7 +2418,7 @@ extern const char *Hello(struct monst *); extern const char *Goodbye(void); extern const struct Race *character_race(short); extern void genl_player_selection(void); -extern int genl_player_setup(void); +extern int genl_player_setup(int); /* ### rumors.c ### */ diff --git a/src/role.c b/src/role.c index 188125e3c..4301cb97a 100644 --- a/src/role.c +++ b/src/role.c @@ -2101,7 +2101,7 @@ character_race(short pmindex) void genl_player_selection(void) { - if (genl_player_setup()) + if (genl_player_setup(0)) return; /* player cancelled role/race/&c selection, so quit */ @@ -2113,7 +2113,8 @@ genl_player_selection(void) /* ['#else' far below] */ static boolean reset_role_filtering(void); -static winid plsel_startmenu(void); +static winid plsel_startmenu(int, int); +static int maybe_skip_seps(int, int); static void setup_rolemenu(winid, boolean, int, int, int); static void setup_racemenu(winid, boolean, int, int, int); static void setup_gendmenu(winid, boolean, int, int, int); @@ -2127,7 +2128,7 @@ static void setup_algnmenu(winid, boolean, int, int, int); /* guts of tty's player_selection() */ int -genl_player_setup(void) +genl_player_setup(int screenheight) { char pbuf[QBUFSZ]; anything any; @@ -2137,7 +2138,9 @@ genl_player_setup(void) menu_item *selected = 0; int clr = 0; char pick4u = 'n'; + int result = 0; /* assume failure (player chooses to 'quit') */ + gp.program_state.in_role_selection++; /* affects tty menu cleanup */ /* Used to avoid "Is this ok?" if player has already specified all * four facets of role. * Note that rigid_role_checks might force any unspecified facets to @@ -2183,13 +2186,13 @@ genl_player_setup(void) pick4u = yn_function(prompt, (char *) 0, '\0', FALSE); pick4u = lowc(pick4u); if (pick4u == '\033' || pick4u == 'q') /* handle [q] */ - return 0; - if (pick4u == ' ' || pick4u == '\n') /* default is 'y' */ - pick4u = 'y'; - else if (pick4u == '@') /* similar to '-@' on command line */ - pick4u = 'a'; + goto setup_done; + if (pick4u == ' ' || pick4u == '\n' || pick4u == '\r') + pick4u = 'y'; /* default */ + else if (pick4u == '@' || pick4u == '*') + pick4u = 'a'; /* similar to '-@' on command line */ /* TODO? handle response of '?' */ - } while (pick4u != 'y' && pick4u != 'a' && pick4u != 'n'); /* [yna] */ + } while (pick4u != 'y' && pick4u != 'n' && pick4u != 'a'); /* [yna] */ #else /* slightly simpler but more likely to end up being wrapped */ @@ -2203,7 +2206,7 @@ genl_player_setup(void) user's and to 'y', to 'q' */ pick4u = yn_function(prompt, ynaqchars, 'y', FALSE); if (pick4u != 'y' && pick4u != 'a' && pick4u != 'n') - return 0; /* bail */ + goto setup_done; /* bail */ #endif } @@ -2225,15 +2228,19 @@ genl_player_setup(void) k = randrole(FALSE); } } else { + /* 'excess' is used to try to avoid tty pagination */ + int excess = maybe_skip_seps(screenheight, RS_ROLE); + /* prompt for a role */ - win = plsel_startmenu(); + win = plsel_startmenu(screenheight, RS_ROLE); /* populate the menu with role choices */ setup_rolemenu(win, TRUE, RACE, GEND, ALGN); /* add miscellaneous menu entries */ role_menu_extra(ROLE_RANDOM, win, TRUE); any = cg.zeroany; /* separator, not a choice */ - add_menu(win, &nul_glyphinfo, &any, 0, 0, - ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE); + if (excess < 1 || excess > 2) + add_menu(win, &nul_glyphinfo, &any, 0, 0, + ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE); role_menu_extra(RS_RACE, win, FALSE); role_menu_extra(RS_GENDER, win, FALSE); role_menu_extra(RS_ALGNMNT, win, FALSE); @@ -2263,7 +2270,7 @@ genl_player_setup(void) destroy_nhwindow(win), win = WIN_ERR; if (choice == ROLE_NONE) { - return 0; /* selected quit */ + goto setup_done; /* selected quit */ } else if (choice == RS_menu_arg(RS_ALGNMNT)) { ALGN = k = ROLE_NONE; nextpick = RS_ALGNMNT; @@ -2320,7 +2327,7 @@ genl_player_setup(void) } /* Permit the user to pick, if there is more than one */ if (n > 1) { - win = plsel_startmenu(); + win = plsel_startmenu(screenheight, RS_RACE); any = cg.zeroany; /* zero out all bits */ /* populate the menu with role choices */ setup_racemenu(win, TRUE, ROLE, GEND, ALGN); @@ -2348,7 +2355,7 @@ genl_player_setup(void) destroy_nhwindow(win), win = WIN_ERR; if (choice == ROLE_NONE) { - return 0; /* selected quit */ + goto setup_done; /* selected quit */ } else if (choice == RS_menu_arg(RS_ALGNMNT)) { ALGN = k = ROLE_NONE; nextpick = RS_ALGNMNT; @@ -2409,7 +2416,7 @@ genl_player_setup(void) } /* Permit the user to pick, if there is more than one */ if (n > 1) { - win = plsel_startmenu(); + win = plsel_startmenu(screenheight, RS_GENDER); any = cg.zeroany; /* zero out all bits */ /* populate the menu with gender choices */ setup_gendmenu(win, TRUE, ROLE, RACE, ALGN); @@ -2437,7 +2444,7 @@ genl_player_setup(void) destroy_nhwindow(win), win = WIN_ERR; if (choice == ROLE_NONE) { - return 0; /* selected quit */ + goto setup_done; /* selected quit */ } else if (choice == RS_menu_arg(RS_ALGNMNT)) { ALGN = k = ROLE_NONE; nextpick = RS_ALGNMNT; @@ -2496,7 +2503,7 @@ genl_player_setup(void) } /* Permit the user to pick, if there is more than one */ if (n > 1) { - win = plsel_startmenu(); + win = plsel_startmenu(screenheight, RS_ALGNMNT); any = cg.zeroany; /* zero out all bits */ setup_algnmenu(win, TRUE, ROLE, RACE, GEND); role_menu_extra(ROLE_RANDOM, win, TRUE); @@ -2522,7 +2529,7 @@ genl_player_setup(void) destroy_nhwindow(win), win = WIN_ERR; if (choice == ROLE_NONE) { - return 0; /* selected quit */ + goto setup_done; /* selected quit */ } else if (choice == RS_menu_arg(RS_GENDER)) { GEND = k = ROLE_NONE; nextpick = RS_GENDER; @@ -2572,29 +2579,8 @@ genl_player_setup(void) */ getconfirmation = (picksomething && pick4u != 'a' && !flags.randomall); while (getconfirmation) { - win = plsel_startmenu(); + win = plsel_startmenu(screenheight, RS_filter); /* filter: not ROLE */ any = cg.zeroany; /* zero out all bits */ -#if 0 - start_menu(win, MENU_BEHAVE_STANDARD); - any.a_int = 0; - char plbuf[QBUFSZ]; - if (!roles[ROLE].name.f - && ((roles[ROLE].allow & ROLE_GENDMASK) - == (ROLE_MALE | ROLE_FEMALE))) - Sprintf(plbuf, " %s", genders[GEND].adj); - else - *plbuf = '\0'; /* omit redundant gender */ - Snprintf(pbuf, sizeof pbuf, "%s, %s%s %s %s", gp.plname, - aligns[ALGN].adj, plbuf, races[RACE].adj, - (GEND == 1 && roles[ROLE].name.f) ? roles[ROLE].name.f - : roles[ROLE].name.m); - add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, pbuf, - MENU_ITEMFLAGS_NONE); - /* blank separator */ - any.a_int = 0; - add_menu(win, &nul_glyphinfo, &any, 0, 0, - ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE); -#endif /* [ynaq] menu choices */ any.a_int = 1; add_menu(win, &nul_glyphinfo, &any, 'y', 0, @@ -2621,7 +2607,7 @@ genl_player_setup(void) switch (choice) { default: /* 'q' or ESC */ - return 0; /* quit */ + goto setup_done; /* quit */ break; case 3: { /* 'a' */ /* @@ -2631,11 +2617,11 @@ genl_player_setup(void) */ int saveROLE, saveRACE, saveGEND, saveALGN; - iflags.renameinprogress = TRUE; + iflags.renameinprogress = TRUE; /* affects main() in unixmain.c */ /* plnamesuffix() can change any or all of ROLE, RACE, GEND, ALGN; we'll override that and honor only the name */ saveROLE = ROLE, saveRACE = RACE, saveGEND = GEND, saveALGN = ALGN; - *gp.plname = '\0'; + gp.plname[0] = '\0'; plnamesuffix(); /* calls askname() when gp.plname[] is empty */ ROLE = saveROLE, RACE = saveRACE, GEND = saveGEND, ALGN = saveALGN; break; /* getconfirmation is still True */ @@ -2653,10 +2639,13 @@ genl_player_setup(void) getconfirmation = FALSE; break; } - } - + } /* while 'getconfirmation' */ /* Success! */ - return 1; + result = 1; + + setup_done: + gp.program_state.in_role_selection--; + return result; } static boolean @@ -2713,9 +2702,41 @@ reset_role_filtering(void) return (n > 0) ? TRUE : FALSE; } +/* the change in format when this extended role selection was converted from + tty-only to tty+curses+? made the role selection menu require two pages + on a traditional 24-line tty; that wasn't fair to tty, so squeeze out + some blank separator lines from the menu if that will make it fit on one */ +static int +maybe_skip_seps(int rows, int aspect) +{ + int i, n = 0; + + /* not much point to generalizing this to other aspects */ + if (aspect != RS_ROLE) + return 0; + /* + * If there are one or two excess lines, setup_rolemenu() will omit + * the separator between 'random' and 'pick race first'. If there are + * two, plsel_startmenu() will omit the one between role info so far + * (" ...") and the set of role entries. + */ + + n += 4; /* title and ensuing separator, role info so far and separator */ + for (i = 0; roles[i].name.m; ++i) + if (ok_role(i, RACE, GEND, ALGN) && ok_race(i, RACE, GEND, ALGN) + && ok_gend(i, RACE, GEND, ALGN) && ok_align(i, RACE, GEND, ALGN)) + ++n; + n += 2; /* 'random' and separator */ + n += 5; /* race 1st, gender 1st, alignment 1st, reset filter, quit */ + n += 1; /* footer/prompt */ + if (rows > 0 && n > rows) + return n - rows; + return 0; +} + /* start a menu; show role aspects specified so far as a header line */ static winid -plsel_startmenu(void) +plsel_startmenu(int ttyrows, int aspect) { char qbuf[QBUFSZ]; winid win; @@ -2730,7 +2751,7 @@ plsel_startmenu(void) rolename = (ROLE < 0) ? "" : (GEND == 1 && roles[ROLE].name.f) ? roles[ROLE].name.f : roles[ROLE].name.m; - if (!*gp.plname || ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0) { + if (!gp.plname[0] || ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0) { /* " " */ Sprintf(qbuf, "%.20s %.20s %.20s %.20s", rolename, @@ -2755,8 +2776,9 @@ plsel_startmenu(void) any = cg.zeroany; add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, qbuf, MENU_ITEMFLAGS_NONE); - add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, - "", MENU_ITEMFLAGS_NONE); + if (maybe_skip_seps(ttyrows, aspect) != 2) + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, + "", MENU_ITEMFLAGS_NONE); return win; } @@ -2930,7 +2952,7 @@ setup_algnmenu( #else /* !TTY_GRAPHICS */ int -genl_player_setup(void) +genl_player_setup(int screenheight UNUSED) { return 0; } diff --git a/win/curses/cursmain.c b/win/curses/cursmain.c index ab018c711..3d370e872 100644 --- a/win/curses/cursmain.c +++ b/win/curses/cursmain.c @@ -275,7 +275,7 @@ void curses_player_selection(void) { #if 1 - if (genl_player_setup()) + if (genl_player_setup(0)) return; /* success */ /* quit/cancel */ diff --git a/win/tty/wintty.c b/win/tty/wintty.c index fb0846cf6..95ce1aa6d 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -575,7 +575,7 @@ tty_preference_update(const char *pref) void tty_player_selection(void) { - if (genl_player_setup()) + if (genl_player_setup(ttyDisplay->rows)) return; bail((char *) 0); @@ -1859,14 +1859,22 @@ tty_dismiss_nhwindow(winid window) case NHW_MENU: case NHW_TEXT: if (cw->active) { + /* skip erasure if window_inited has been reset to 0 during + final run-down in case this is the end-of-game window; + the contents of that window should remain shown even when + the window itself has gone away */ if (iflags.window_inited) { - /* otherwise dismissing the text endwin after other windows - * are dismissed tries to redraw the map and panics. since - * the whole reason for dismissing the other windows was to - * leave the ending window on the screen, we don't want to - * erase it anyway. - */ - erase_menu_or_text(window, cw, FALSE); + boolean clearscreen = FALSE; /* just erase the menu */ + + /* during role/race/&c selection, menus are put up on top + of the base window; we don't track what was there so + can't refresh--force the screen to be cleared instead + (affects dismissal of 'reset role filtering' menu if + screen height forces that to need a second page) */ + if (gp.program_state.in_role_selection) + clearscreen = TRUE; + + erase_menu_or_text(window, cw, clearscreen); } cw->active = 0; }