From 656f58b099e1d318401d812303c66559d06590f6 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Sun, 30 Nov 2025 06:49:14 +0000 Subject: [PATCH] A basic version of starting character rerolling This adds a "reroll" option that lets players reroll their character's attributes and starting inventory. Although I generally think doing this makes the game worse, a) some players are going to do it regardless and b) if a player is going for a challenge game, rather than to win, it may be required. So in the absence of an option like this, players repeatedly start and quit games instead, creating a large number of junk logfile entries and generally causing problems for other players on the same shared machine (because repeatedly reloading the game is very CPU-intensive). This should in theory be windowport-agnostic (although in practice it may not be). Tested on tty, X11 and curses; on tty and X11 it works fine (although X11 treats the change in attributes as something that needs a status highlight), on curses it is slightly jankier in terms of what other windows are drawn in the background (but still plays correctly and I suspect this is a pre-existing bug). To form a complete implementation, we will need to consider the following: - Should there be a delay on a) starting the game and/or b) rerolling? If so, what should it be (maybe configurable via sysconf?) - Should we take more steps to discourage players from rerolling? It would be bad if players see the option exists and turn it on just because it exists, or (worse) treat it as condoning the particular style of play. - Should we take steps to detect that players are rerolling manually and a) tell them to use the option instead, b) tell them that this is not an intended way to play (and may make the game less enjoyable and/or prevent them getting the practice they need to eventually win)? Breaks save and bones files. --- dat/opthelp | 1 + doc/Guidebook.mn | 10 ++++++ include/extern.h | 2 ++ include/optlist.h | 3 ++ include/patchlevel.h | 2 +- include/you.h | 12 ++++--- src/allmain.c | 9 ++++-- src/botl.c | 3 +- src/insight.c | 6 ++++ src/invent.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ src/topten.c | 4 +++ 11 files changed, 117 insertions(+), 10 deletions(-) diff --git a/dat/opthelp b/dat/opthelp index d36e5228b..1fcb803d9 100644 --- a/dat/opthelp +++ b/dat/opthelp @@ -56,6 +56,7 @@ pushweapon when wielding a new weapon, put your previously [False] quick_farsight usually skip the chance to browse the map when [False] randomly triggered clairvoyance takes place rawio allow the use of raw I/O [False] +reroll allow rerolling of starting inventory [False] rest_on_space count the space bar as a rest character [False] safe_pet prevent you from (knowingly) attacking your pet(s) [True] safe_wait require use of 'm' prefix before '.' or 's' to [True] diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 95bd7be35..72d3f3be3 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -4556,6 +4556,16 @@ If \f(CRrace\fP is not specified, there is no default value; player will be prompted unless role forces a choice for race. Cannot be set with the \(oq\f(CRO\fP\(cq command. Persistent. +.lp reroll +Allows rerolling your character's starting inventory and attributes (default +false). Persistent. +.lp "" +Note that rerolling your character is not a recommended way to play if aiming +merely to win (a lucky start has a much smaller influence on whether or not +you win the game than your actions later in the game). This option exists +partly as an acknowledgement that some players will insist on doing so anyway, +and partly because rerolling may be necessary for certain types of challenge +games. .lp rest_on_space Make the space bar a synonym for the \(oq.\(cq (#wait) command (default off). Persistent. diff --git a/include/extern.h b/include/extern.h index 831e209db..59e7188af 100644 --- a/include/extern.h +++ b/include/extern.h @@ -258,6 +258,7 @@ extern void free_ebones(struct monst *) NONNULLARG1; /* ### botl.c ### */ +extern char *get_strength_str(void); extern char *do_statusline1(void); extern void check_gold_symbol(void); extern char *do_statusline2(void); @@ -2406,6 +2407,7 @@ extern int query_category(const char *, struct obj *, int, menu_item **, int) NO /* dotypeinv() call query_objlist with NULL arg1 */ extern int query_objlist(const char *, struct obj **, int, menu_item **, int, boolean(*)(struct obj *)) NONNULLARG24; +extern boolean reroll_menu(void); extern struct obj *pick_obj(struct obj *) NONNULLARG1; extern void encumber_msg(void); extern int container_at(coordxy, coordxy, boolean); diff --git a/include/optlist.h b/include/optlist.h index b0bc715f3..0625b1e51 100644 --- a/include/optlist.h +++ b/include/optlist.h @@ -608,6 +608,9 @@ static int optfn_##a(int, int, boolean, char *, char *); Off, No, No, No, NoAlias, (boolean *) 0, Term_False, (char *)0) #endif + NHOPTB(reroll, Advanced, 0, opt_in, set_in_config, + Off, Yes, No, No, NoAlias, &u.uroleplay.reroll, Term_False, + "allow rerolling of starting inventory and items") NHOPTB(rest_on_space, Advanced, 0, opt_in, set_in_game, Off, Yes, No, No, NoAlias, &flags.rest_on_space, Term_False, "space bar is bound to the rest-command") diff --git a/include/patchlevel.h b/include/patchlevel.h index 42485fef6..8034d40d4 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -17,7 +17,7 @@ * Incrementing EDITLEVEL can be used to force invalidation of old bones * and save files. */ -#define EDITLEVEL 131 +#define EDITLEVEL 132 /* * Development status possibilities. diff --git a/include/you.h b/include/you.h index efa1baa69..a073e5c57 100644 --- a/include/you.h +++ b/include/you.h @@ -163,11 +163,13 @@ struct u_conduct { /* number of times... */ }; struct u_roleplay { - boolean blind; /* permanently blind */ - boolean nudist; /* has not worn any armor, ever */ - boolean deaf; /* permanently deaf */ - boolean pauper; /* no starting inventory */ - long numbones; /* # of bones files loaded */ + boolean blind; /* permanently blind */ + boolean nudist; /* has not worn any armor, ever */ + boolean deaf; /* permanently deaf */ + boolean pauper; /* no starting inventory */ + boolean reroll; /* starting inventory/attr rerolling enabled */ + long numbones; /* # of bones files loaded */ + long numrerolls; /* # of rerolls used */ }; /*** Unified structure containing role information ***/ diff --git a/src/allmain.c b/src/allmain.c index 6ced3e372..4f5e8cbb6 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -809,11 +809,16 @@ newgame(void) (void) makedog(); u_init_inventory_attrs(); - u_init_skills_discoveries(); docrt(); + flush_screen(1); + bot(); + while (u.uroleplay.reroll && reroll_menu()) { + u_init_inventory_attrs(); + bot(); + } + u_init_skills_discoveries(); if (flags.legacy) { - flush_screen(1); com_pager(u.uroleplay.pauper ? "pauper_legacy" : "legacy"); } diff --git a/src/botl.c b/src/botl.c index f3ad98aa5..f90812f53 100644 --- a/src/botl.c +++ b/src/botl.c @@ -19,9 +19,8 @@ const char *const enc_stat[] = { staticfn const char *rank(void); staticfn void bot_via_windowport(void); staticfn void stat_update_time(void); -staticfn char *get_strength_str(void); -staticfn char * +char * get_strength_str(void) { static char buf[32]; diff --git a/src/insight.c b/src/insight.c index 9f0f71059..9f3763619 100644 --- a/src/insight.c +++ b/src/insight.c @@ -2109,6 +2109,12 @@ show_conduct(int final) if (u.uroleplay.pauper) enl_msg(You_, gi.invent ? "started" : "are", "started out", " without possessions", ""); + if (u.uroleplay.reroll) { + Sprintf(buf, "rerolled your character %ld time%s", + u.uroleplay.numrerolls, plur(u.uroleplay.numrerolls)); + you_have_X(buf); + } + /* nudist is far more than a subset of possessionless, and a much more impressive accomplishment, but showing "started out without possessions" before "faithfully nudist" looks more logical */ diff --git a/src/invent.c b/src/invent.c index abb7d7c39..ddd7ffefb 100644 --- a/src/invent.c +++ b/src/invent.c @@ -2522,6 +2522,81 @@ askchain( return cnt; } + +/* The menu for rerolling attributes and inventory. + + This is similar to the other inventory menus, but simpler to help it fit on + the screen (there's more text around it and rerolling is difficult if you + can't see the whole list at once). + + Returns TRUE (and increases numrerolls) if a reroll was requested. */ +boolean +reroll_menu(void) +{ + winid win; + anything any; + menu_item *pick_list = NULL; + struct obj *otmp; + int tmpglyph; + glyph_info tmpglyphinfo; + char option; + char buf[BUFSZ]; + + win = create_nhwindow(NHW_MENU); + start_menu(win, MENU_BEHAVE_STANDARD); + any = cg.zeroany; + + any.a_char = 'n'; + add_menu(win, &nul_glyphinfo, &any, flags.lootabc ? 0 : 'p', 0, + ATR_NONE, NO_COLOR, "start the game with this character", + MENU_ITEMFLAGS_NONE); + any.a_char = 'y'; + add_menu(win, &nul_glyphinfo, &any, flags.lootabc ? 0 : 'r', 0, + ATR_NONE, NO_COLOR, "reroll another character", + MENU_ITEMFLAGS_NONE); + any.a_char = 0; + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, "", + MENU_ITEMFLAGS_NONE); + + ++gd.distantname; /* avoid adding items to discoveries */ + ++iflags.override_ID; /* identify them */ + for (otmp = gi.invent; otmp; otmp = otmp->nobj) { + tmpglyph = obj_to_glyph(otmp, rn2_on_display_rng); + map_glyphinfo(0, 0, tmpglyph, 0U, &tmpglyphinfo); + add_menu(win, &tmpglyphinfo, &any, 0, 0, + ATR_NONE, NO_COLOR, doname(otmp), MENU_ITEMFLAGS_NONE); + } + --iflags.override_ID; + --gd.distantname; + + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, "", + MENU_ITEMFLAGS_NONE); + Sprintf(buf, "St:%s Dx:%-1d Co:%-1d In:%-1d Wi:%-1d Ch:%-1d", + get_strength_str(), + ACURR(A_DEX), ACURR(A_CON), ACURR(A_INT), ACURR(A_WIS), + ACURR(A_CHA)); + add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, NO_COLOR, + buf, MENU_ITEMFLAGS_NONE); + + end_menu(win, "Reroll this character?"); + if (select_menu(win, PICK_ONE, &pick_list) > 0) { + option = pick_list[0].item.a_char; + free((genericptr_t) pick_list); + } else { + /* user closed the menu without selecting; unclear what their choice + is here so ask again; but (e.g. for hangup handling) stop asking if + the user cancels out again */ + option = y_n("Reroll this character?"); + } + destroy_nhwindow(win); + + if (option == 'y') { + ++u.uroleplay.numrerolls; + return TRUE; + } + return FALSE; +} + /* * Object identification routines: */ diff --git a/src/topten.c b/src/topten.c index cb968a726..d6567ea08 100644 --- a/src/topten.c +++ b/src/topten.c @@ -385,6 +385,7 @@ writexlentry(FILE *rfile, struct toptenentry *tt, int how) Fprintf(rfile, "%cwish_cnt=%ld", XLOG_SEP, u.uconduct.wishes); Fprintf(rfile, "%carti_wish_cnt=%ld", XLOG_SEP, u.uconduct.wisharti); Fprintf(rfile, "%cbones=%ld", XLOG_SEP, u.uroleplay.numbones); + Fprintf(rfile, "%crerolls=%ld", XLOG_SEP, u.uroleplay.numrerolls); Fprintf(rfile, "\n"); #undef XLOG_SEP } @@ -400,6 +401,8 @@ encodexlogflags(void) e |= 1L << 1; if (!u.uroleplay.numbones) e |= 1L << 2; + if (u.uroleplay.reroll) + e |= 1L << 3; return e; } @@ -601,6 +604,7 @@ encode_extended_conducts(char *buf) add_achieveX(buf, "pauper", u.uroleplay.pauper); add_achieveX(buf, "bonesless", !flags.bones); add_achieveX(buf, "petless", !u.uconduct.pets); + add_achieveX(buf, "unrerolled", !u.uroleplay.reroll); return buf; }