From b186c7e87c0d5947d8998b5eca2a04f1a7f06ad8 Mon Sep 17 00:00:00 2001 From: kinther Date: Tue, 30 Dec 2025 08:22:48 -0800 Subject: [PATCH] Reroll update --- README.md | 1 + lib/text/help/help.hlp | 10 +++- src/act.h | 1 + src/act.other.c | 107 +++++++++++++++++++++++++++++++++++++++++ src/interpreter.c | 2 +- src/pfdefaults.h | 2 + src/players.c | 29 +++++++++++ src/structs.h | 3 ++ src/utils.h | 3 ++ 9 files changed, 156 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index edf5324..5982e3d 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Changes in v1.1.0-alpha: * PC's and NPC's can now have an age set between 18-65 * "audit ac" command for immortals (formerly "acaudit"), allowing for further audit commands in the future * Minor score output change to only show quest status while on a quest, PC/NPC name, sdesc, and current ldesc + * Added ability to reroll initial stats if they are not to player's liking, and undo reroll if needed Features to be implemented in the next few releases: diff --git a/lib/text/help/help.hlp b/lib/text/help/help.hlp index e78109d..5d90e7a 100644 --- a/lib/text/help/help.hlp +++ b/lib/text/help/help.hlp @@ -7962,7 +7962,15 @@ From: justo #2 REROLL -Usage: reroll +Usage: reroll + reroll undo + +Reroll lets you roll a new set of stats once. Your original stats are saved +for 2 hours; during that window you may use 'reroll undo' to restore them. +After the review period or after using reroll undo, your stats are permanent +and you cannot reroll again. + +Immortals: reroll rerolls that player's stats. REROLL gives a player new stats (i.e., Str, Int, Wis, Dex, Con, and Cha) diff --git a/src/act.h b/src/act.h index 732141d..fc47183 100644 --- a/src/act.h +++ b/src/act.h @@ -258,6 +258,7 @@ ACMD(do_hide); ACMD(do_listen); ACMD(do_not_here); ACMD(do_change); +ACMD(do_reroll); ACMD(do_report); ACMD(do_save); ACMD(do_skills); diff --git a/src/act.other.c b/src/act.other.c index a61dae5..1f58185 100644 --- a/src/act.other.c +++ b/src/act.other.c @@ -66,6 +66,14 @@ static void change_trim_trailing_spaces(char *text) } } +#define REROLL_REVIEW_SECONDS (2 * SECS_PER_REAL_HOUR) + +static void reroll_clear_saved(struct char_data *ch) +{ + GET_REROLL_EXPIRES(ch) = 0; + memset(&GET_REROLL_OLD_ABILS(ch), 0, sizeof(struct char_ability_data)); +} + ACMD(do_quit) { char first[MAX_INPUT_LENGTH]; @@ -275,6 +283,105 @@ ACMD(do_change) send_to_char(ch, "Long description updated.\r\n"); } +ACMD(do_reroll) +{ + char arg[MAX_INPUT_LENGTH]; + struct char_data *vict; + time_t now; + time_t remaining; + bool expired = FALSE; + + one_argument(argument, arg); + + if (IS_NPC(ch)) { + send_to_char(ch, "You can't reroll stats.\r\n"); + return; + } + + now = time(0); + if (GET_REROLL_EXPIRES(ch) > 0 && now >= GET_REROLL_EXPIRES(ch)) { + reroll_clear_saved(ch); + save_char(ch); + expired = TRUE; + } + + if (*arg && is_abbrev(arg, "undo")) { + if (!GET_REROLL_USED(ch)) { + send_to_char(ch, "You haven't rerolled yet.\r\n"); + return; + } + if (GET_REROLL_EXPIRES(ch) <= 0 || expired) { + send_to_char(ch, "You no longer have a reroll pending; your stats are permanent.\r\n"); + return; + } + + ch->real_abils = GET_REROLL_OLD_ABILS(ch); + affect_total(ch); + reroll_clear_saved(ch); + save_char(ch); + + send_to_char(ch, "Your original stats have been restored. You cannot reroll again.\r\n"); + send_to_char(ch, "Stats: Str %d, Int %d, Wis %d, Dex %d, Con %d, Cha %d\r\n", + GET_STR(ch), GET_INT(ch), GET_WIS(ch), + GET_DEX(ch), GET_CON(ch), GET_CHA(ch)); + return; + } + + if (*arg && GET_LEVEL(ch) >= LVL_GRGOD) { + if (!(vict = get_char_vis(ch, arg, NULL, FIND_CHAR_WORLD))) + send_to_char(ch, "There is no such player.\r\n"); + else if (IS_NPC(vict)) + send_to_char(ch, "You can't do that to a mob!\r\n"); + else { + roll_real_abils(vict); + affect_total(vict); + send_to_char(ch, "Rerolled...\r\n"); + log("(GC) %s has rerolled %s.", GET_NAME(ch), GET_NAME(vict)); + send_to_char(ch, "New stats: Str %d, Int %d, Wis %d, Dex %d, Con %d, Cha %d\r\n", + GET_STR(vict), GET_INT(vict), GET_WIS(vict), + GET_DEX(vict), GET_CON(vict), GET_CHA(vict)); + save_char(vict); + } + return; + } + + if (*arg) { + send_to_char(ch, "Usage: reroll | reroll undo\r\n"); + return; + } + + if (GET_REROLL_USED(ch)) { + if (GET_REROLL_EXPIRES(ch) > 0 && now < GET_REROLL_EXPIRES(ch)) { + remaining = GET_REROLL_EXPIRES(ch) - now; + int hours = remaining / SECS_PER_REAL_HOUR; + int mins = (remaining % SECS_PER_REAL_HOUR) / SECS_PER_REAL_MIN; + + if (hours > 0) + send_to_char(ch, "You have already rerolled. You can 'reroll undo' within %d hour%s %d minute%s.\r\n", + hours, hours == 1 ? "" : "s", mins, mins == 1 ? "" : "s"); + else + send_to_char(ch, "You have already rerolled. You can 'reroll undo' within %d minute%s.\r\n", + mins, mins == 1 ? "" : "s"); + } else { + send_to_char(ch, "You have already rerolled and cannot reroll again.\r\n"); + } + return; + } + + GET_REROLL_OLD_ABILS(ch) = ch->real_abils; + roll_real_abils(ch); + affect_total(ch); + GET_REROLL_USED(ch) = TRUE; + GET_REROLL_EXPIRES(ch) = now + REROLL_REVIEW_SECONDS; + save_char(ch); + + send_to_char(ch, "Your stats have been rerolled. You have 2 hours to review them.\r\n"); + send_to_char(ch, "New stats: Str %d, Int %d, Wis %d, Dex %d, Con %d, Cha %d\r\n", + GET_STR(ch), GET_INT(ch), GET_WIS(ch), + GET_DEX(ch), GET_CON(ch), GET_CHA(ch)); + send_to_char(ch, "Use 'reroll undo' to restore your original stats before the timer expires.\r\n"); +} + /* Generic function for commands which are normally overridden by special * procedures - i.e., shop commands, mail commands, etc. */ ACMD(do_not_here) diff --git a/src/interpreter.c b/src/interpreter.c index 6666667..64b7687 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -264,7 +264,7 @@ cpp_extern const struct command_info cmd_info[] = { { "recent" , "recent" , POS_DEAD , do_recent , LVL_IMMORT, 0 }, { "remove" , "rem" , POS_RESTING , do_remove , 0, 0 }, { "report" , "repo" , POS_RESTING , do_report , 0, 0 }, - { "reroll" , "rero" , POS_DEAD , do_wizutil , LVL_GRGOD, SCMD_REROLL }, + { "reroll" , "rero" , POS_DEAD , do_reroll , 0, 0 }, { "rescue" , "resc" , POS_FIGHTING, do_rescue , 1, 0 }, { "restore" , "resto" , POS_DEAD , do_restore , LVL_GOD, 0 }, { "return" , "retu" , POS_DEAD , do_return , 0, 0 }, diff --git a/src/pfdefaults.h b/src/pfdefaults.h index 36d30f2..27bf28e 100644 --- a/src/pfdefaults.h +++ b/src/pfdefaults.h @@ -58,5 +58,7 @@ #define PFDEF_CURRQUEST NOTHING #define PFDEF_LASTMOTD 0 #define PFDEF_LASTNEWS 0 +#define PFDEF_REROLL_USED 0 +#define PFDEF_REROLL_EXPIRES 0 #endif /* _PFDEFAULTS_H_ */ diff --git a/src/players.c b/src/players.c index 8e50050..b149aaf 100644 --- a/src/players.c +++ b/src/players.c @@ -315,6 +315,9 @@ int load_char(const char *name, struct char_data *ch) GET_NUM_QUESTS(ch) = PFDEF_COMPQUESTS; GET_LAST_MOTD(ch) = PFDEF_LASTMOTD; GET_LAST_NEWS(ch) = PFDEF_LASTNEWS; + GET_REROLL_USED(ch) = PFDEF_REROLL_USED; + GET_REROLL_EXPIRES(ch) = PFDEF_REROLL_EXPIRES; + memset(&GET_REROLL_OLD_ABILS(ch), 0, sizeof(struct char_ability_data)); if (GET_ACCOUNT(ch)) { free(GET_ACCOUNT(ch)); GET_ACCOUNT(ch) = NULL; @@ -454,6 +457,20 @@ int load_char(const char *name, struct char_data *ch) case 'R': if (!strcmp(tag, "Room")) GET_LOADROOM(ch) = atoi(line); + else if (!strcmp(tag, "RrUs")) GET_REROLL_USED(ch) = atoi(line); + else if (!strcmp(tag, "RrTm")) GET_REROLL_EXPIRES(ch) = (time_t)atol(line); + else if (!strcmp(tag, "RrAb")) { + int rstr, rint, rwis, rdex, rcon, rcha; + + if (sscanf(line, "%d %d %d %d %d %d", &rstr, &rint, &rwis, &rdex, &rcon, &rcha) == 6) { + GET_REROLL_OLD_ABILS(ch).str = rstr; + GET_REROLL_OLD_ABILS(ch).intel = rint; + GET_REROLL_OLD_ABILS(ch).wis = rwis; + GET_REROLL_OLD_ABILS(ch).dex = rdex; + GET_REROLL_OLD_ABILS(ch).con = rcon; + GET_REROLL_OLD_ABILS(ch).cha = rcha; + } + } break; case 'S': @@ -663,6 +680,18 @@ void save_char(struct char_data * ch) fprintf(fl, "Lmot: %d\n", (int)GET_LAST_MOTD(ch)); if (GET_LAST_NEWS(ch) != PFDEF_LASTNEWS) fprintf(fl, "Lnew: %d\n", (int)GET_LAST_NEWS(ch)); + if (GET_REROLL_USED(ch) != PFDEF_REROLL_USED) + fprintf(fl, "RrUs: %d\n", (int)GET_REROLL_USED(ch)); + if (GET_REROLL_EXPIRES(ch) != PFDEF_REROLL_EXPIRES) { + fprintf(fl, "RrTm: %ld\n", (long)GET_REROLL_EXPIRES(ch)); + fprintf(fl, "RrAb: %d %d %d %d %d %d\n", + GET_REROLL_OLD_ABILS(ch).str, + GET_REROLL_OLD_ABILS(ch).intel, + GET_REROLL_OLD_ABILS(ch).wis, + GET_REROLL_OLD_ABILS(ch).dex, + GET_REROLL_OLD_ABILS(ch).con, + GET_REROLL_OLD_ABILS(ch).cha); + } if (GET_HOST(ch)) fprintf(fl, "Host: %s\n", GET_HOST(ch)); if (GET_HEIGHT(ch) != PFDEF_HEIGHT) fprintf(fl, "Hite: %d\n", GET_HEIGHT(ch)); diff --git a/src/structs.h b/src/structs.h index fdb09e0..7d753b4 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1052,6 +1052,9 @@ struct player_special_data_saved int quest_counter; /**< Count of targets left to get */ time_t lastmotd; /**< Last time player read motd */ time_t lastnews; /**< Last time player read news */ + bool reroll_used; /**< Has the PC used their one-time reroll */ + time_t reroll_expires; /**< When the reroll review period ends */ + struct char_ability_data reroll_old_abils; /**< Original stats before reroll */ time_t next_skill_gain[MAX_SKILLS+1]; /* indexed by skill/spell number */ }; diff --git a/src/utils.h b/src/utils.h index 8702064..2fd607d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -678,6 +678,9 @@ do \ #define GET_SCAN_RESULTS(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->scan_results)) #define GET_LAST_MOTD(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.lastmotd)) #define GET_LAST_NEWS(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.lastnews)) +#define GET_REROLL_USED(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.reroll_used)) +#define GET_REROLL_EXPIRES(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.reroll_expires)) +#define GET_REROLL_OLD_ABILS(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.reroll_old_abils)) /** Get channel history i for ch. */ #define GET_HISTORY(ch, i) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.comm_hist[i])) /** Return the page length (height) for ch. */