From 5d4d724e73da3607daef1781e004e2549263bef1 Mon Sep 17 00:00:00 2001 From: kinther Date: Mon, 29 Dec 2025 07:16:47 -0800 Subject: [PATCH] Prioritize stats during chargen --- README.md | 4 +- src/class.c | 103 +++++++++++++++++++++++++++++++++++- src/interpreter.c | 129 +++++++++++++++++++++++++++++++++++++++++++++- src/structs.h | 5 ++ 4 files changed, 237 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d613d35..48ea3a0 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,12 @@ Features in Miranthas MUD Alpha release: * PC's now use a short description for identification instead of name * Backgrounds are now available for PC's and NPC's * Account system for tracking players/characters over long periods of time + * Race/species selection and stat ranges (elves have higher dex, dwarves have higher str, etc) + * Prioritized stats during character generation Features to be implemented in the next few releases: -* Race/species selection and stat ranges (elves have higher dex, dwarves have higher str, etc) * Subclass selection to personalize character further -* Prioritized stats during character generation * Combat is slowed down so it isn't over in < 15 seconds (unless you're far outmatched) * Mounts added to help with long trips * Wagons added to help with caravans diff --git a/src/class.c b/src/class.c index fafc7a0..9a005d2 100644 --- a/src/class.c +++ b/src/class.c @@ -139,10 +139,103 @@ bool has_save_proficiency(int class_num, int ability) { } } +static void set_real_ability(struct char_data *ch, int ability, int value) +{ + switch (ability) { + case ABIL_STR: ch->real_abils.str = value; break; + case ABIL_DEX: ch->real_abils.dex = value; break; + case ABIL_CON: ch->real_abils.con = value; break; + case ABIL_INT: ch->real_abils.intel = value; break; + case ABIL_WIS: ch->real_abils.wis = value; break; + case ABIL_CHA: ch->real_abils.cha = value; break; + } +} + +static void roll_real_abils_preference(struct char_data *ch) +{ + int i, j, temp; + int rolls[NUM_ABILITIES]; + int sorted[NUM_ABILITIES]; + bool used[NUM_ABILITIES] = { FALSE }; + bool assigned[NUM_ABILITIES] = { FALSE }; + static const int default_order[NUM_ABILITIES] = { + ABIL_STR, ABIL_DEX, ABIL_CON, ABIL_INT, ABIL_WIS, ABIL_CHA + }; + + for (i = 0; i < NUM_ABILITIES; i++) { + int die[4]; + + for (j = 0; j < 4; j++) + die[j] = rand_number(1, 6); + + temp = die[0] + die[1] + die[2] + die[3] - + MIN(die[0], MIN(die[1], MIN(die[2], die[3]))); + + rolls[i] = temp; + sorted[i] = temp; + } + + for (i = 0; i < NUM_ABILITIES - 1; i++) { + for (j = i + 1; j < NUM_ABILITIES; j++) { + if (sorted[j] > sorted[i]) { + temp = sorted[i]; + sorted[i] = sorted[j]; + sorted[j] = temp; + } + } + } + + int pref_count = ch->stat_pref_count; + if (pref_count > NUM_ABILITIES) + pref_count = NUM_ABILITIES; + + for (i = 0; i < pref_count; i++) { + int ability = ch->stat_pref_order[i]; + + if (ability < 0 || ability >= NUM_ABILITIES) + continue; + + set_real_ability(ch, ability, sorted[i]); + assigned[ability] = TRUE; + + for (j = 0; j < NUM_ABILITIES; j++) { + if (!used[j] && rolls[j] == sorted[i]) { + used[j] = TRUE; + break; + } + } + } + + int fifo_idx = 0; + for (i = 0; i < NUM_ABILITIES; i++) { + int ability = default_order[i]; + + if (assigned[ability]) + continue; + + while (fifo_idx < NUM_ABILITIES && used[fifo_idx]) + fifo_idx++; + if (fifo_idx >= NUM_ABILITIES) + break; + + set_real_ability(ch, ability, rolls[fifo_idx]); + used[fifo_idx] = TRUE; + assigned[ability] = TRUE; + fifo_idx++; + } + + if (HAS_VALID_SPECIES(ch)) + apply_species_bonuses(ch); + else + ch->aff_abils = ch->real_abils; + + ch->stat_pref_use = FALSE; +} + /* Roll the 6 stats for a character... each stat is made of the sum of the best * 3 out of 4 rolls of a 6-sided die. Each class then decides which priority * will be given for the best to worst stats. */ -void roll_real_abils(struct char_data *ch) +static void roll_real_abils_classic(struct char_data *ch) { int i, j, k, temp; ubyte table[6]; @@ -238,6 +331,14 @@ void roll_real_abils(struct char_data *ch) ch->aff_abils = ch->real_abils; } +void roll_real_abils(struct char_data *ch) +{ + if (ch && ch->stat_pref_use) + roll_real_abils_preference(ch); + else + roll_real_abils_classic(ch); +} + /* Per-class skill caps */ #define DEFAULT_CLASS_SKILL_MAX 90 diff --git a/src/interpreter.c b/src/interpreter.c index 073aaf0..51fa507 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -49,6 +49,10 @@ static bool perform_new_char_dupe_check(struct descriptor_data *d); /* sort_commands utility */ static int sort_commands_helper(const void *a, const void *b); static void show_species_menu(struct descriptor_data *d); +static bool is_creation_state(int state); +static void show_stat_pref_prompt(struct descriptor_data *d); +static int ability_from_pref_arg(const char *arg); +static bool parse_stat_preference(char *input, ubyte *order, ubyte *count); /* globals defined here, used here and elsewhere */ int *cmd_sort_info = NULL; @@ -1270,7 +1274,7 @@ static bool perform_new_char_dupe_check(struct descriptor_data *d) /* Do the player names match? */ if (!strcmp(GET_NAME(k->character), GET_NAME(d->character))) { /* Check the other character is still in creation? */ - if ((STATE(k) > CON_PLAYING) && (STATE(k) < CON_QCLASS)) { + if (is_creation_state(STATE(k))) { /* Boot the older one */ k->character->desc = NULL; k->character = NULL; @@ -1317,6 +1321,107 @@ static void show_species_menu(struct descriptor_data *d) write_to_output(d, "Species: "); } +static bool is_creation_state(int state) +{ + switch (state) { + case CON_GET_NAME: + case CON_NAME_CNFRM: + case CON_PASSWORD: + case CON_NEWPASSWD: + case CON_CNFPASSWD: + case CON_QSEX: + case CON_QSPECIES: + case CON_QCLASS: + case CON_QSTAT_PREF: + case CON_QSHORTDESC: + case CON_PLR_DESC: + case CON_PLR_BACKGROUND: + return TRUE; + default: + return FALSE; + } +} + +static void show_stat_pref_prompt(struct descriptor_data *d) +{ + write_to_output(d, + "\r\nEnter your stat preference, with the first stat being your preferred highest,\r\n" + "followed by the others in descending order.\r\n" + "If you list fewer than six, those listed get the highest rolls; the rest are FIFO.\r\n" + "Example: strength dexterity constitution intelligence wisdom charisma\r\n" + " or: str dex con int wis cha\r\n" + "Press Enter to skip (first-in, first-out).\r\n" + "Stat preference: "); +} + +static int ability_from_pref_arg(const char *arg) +{ + if (!arg || !*arg) + return -1; + if (!str_cmp(arg, "str") || is_abbrev(arg, "strength")) + return ABIL_STR; + if (!str_cmp(arg, "dex") || is_abbrev(arg, "dexterity")) + return ABIL_DEX; + if (!str_cmp(arg, "con") || is_abbrev(arg, "constitution")) + return ABIL_CON; + if (!str_cmp(arg, "int") || is_abbrev(arg, "intelligence")) + return ABIL_INT; + if (!str_cmp(arg, "wis") || is_abbrev(arg, "wisdom")) + return ABIL_WIS; + if (!str_cmp(arg, "cha") || is_abbrev(arg, "charisma")) + return ABIL_CHA; + return -1; +} + +static bool parse_stat_preference(char *input, ubyte *order, ubyte *count) +{ + char arg[MAX_INPUT_LENGTH]; + bool seen[NUM_ABILITIES] = { FALSE }; + + if (!order || !count) + return FALSE; + + *count = 0; + skip_spaces(&input); + if (!*input) + return TRUE; + + if (!str_cmp(input, "none") || !str_cmp(input, "no") || !str_cmp(input, "skip")) + return TRUE; + + while (*input) { + size_t len; + int ability; + + input = one_argument(input, arg); + if (!*arg) + break; + + len = strlen(arg); + while (len > 0 && (arg[len - 1] == ',' || arg[len - 1] == '.')) { + arg[len - 1] = '\0'; + len--; + } + + if (!*arg) + continue; + + ability = ability_from_pref_arg(arg); + if (ability < 0 || ability >= NUM_ABILITIES) + return FALSE; + if (seen[ability]) + return FALSE; + if (*count >= NUM_ABILITIES) + return FALSE; + + order[*count] = (ubyte)ability; + (*count)++; + seen[ability] = TRUE; + } + + return TRUE; +} + /* load the player, put them in the right room - used by copyover_recover too */ int enter_player_game (struct descriptor_data *d) { @@ -1912,6 +2017,27 @@ case CON_QCLASS: GET_CLASS(d->character) = load_result; } + show_stat_pref_prompt(d); + STATE(d) = CON_QSTAT_PREF; + return; + +case CON_QSTAT_PREF: { + ubyte order[NUM_ABILITIES]; + ubyte count = 0; + + if (!parse_stat_preference(arg, order, &count)) { + write_to_output(d, + "\r\nInvalid stat list. Please enter a valid order, or press Enter to skip.\r\n"); + show_stat_pref_prompt(d); + return; + } + + d->character->stat_pref_use = TRUE; + d->character->stat_pref_count = count; + for (int i = 0; i < NUM_ABILITIES; i++) { + d->character->stat_pref_order[i] = (i < count) ? order[i] : 0; + } + /* Create player entry and initialize character now so file exists */ if (d->olc) { free(d->olc); @@ -1958,6 +2084,7 @@ case CON_QCLASS: STATE(d) = CON_QSHORTDESC; return; +} case CON_QSHORTDESC: { skip_spaces(&arg); diff --git a/src/structs.h b/src/structs.h index d396bbd..2341eea 100644 --- a/src/structs.h +++ b/src/structs.h @@ -347,6 +347,7 @@ #define CON_QSEX 7 /**< Choose character sex */ #define CON_QSPECIES 8 /**< Choose character species */ #define CON_QCLASS 9 /**< Choose character class */ +#define CON_QSTAT_PREF 44 /**< Choose character stat preference order */ #define CON_QSHORTDESC 10 /**< Enter a new character short description prompt */ #define CON_RMOTD 11 /**< Reading the message of the day */ #define CON_MENU 12 /**< At the main menu */ @@ -1162,6 +1163,10 @@ struct char_data struct group_data *group; /**< Character's Group */ long pref; /**< unique session id */ + + bool stat_pref_use; /**< Use stat preference ordering when rolling abilities */ + ubyte stat_pref_count; /**< Number of preferred stats entered */ + ubyte stat_pref_order[NUM_ABILITIES]; /**< Ability order preferences */ struct list_data * events; };