From 178865989310bad20079a1650349821dd0727998 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Tue, 30 Dec 2025 12:52:59 -0600 Subject: [PATCH] playtestable experiment: smoother experience curve --- src/db.cpp | 5 +++- src/db.hpp | 1 + src/players.cpp | 13 ++++++++++- src/utils.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++--- src/utils.hpp | 1 + 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index fa20f6a7..451db920 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -195,11 +195,14 @@ long get_set_exp(int level, int race, int class_num, int zone) int cfactor = 100; int sfactor = 100; int zfactor = 100; + /* all current zones have a factor of 100, so skip this */ + /* for (zfactor = 0; zfactor <= top_of_zone_table; zfactor++) { if (zone == zone_table[zfactor].number) break; } zfactor = zone_table[zfactor].zone_factor; + */ /*zfactor = 100; */ /*The cfactor is the factor of class to adjust exp number is percentage @@ -557,7 +560,7 @@ void boot_db(void) { log(" Skills."); init_skills(); - + log("Assigning skills and spells to classes."); assign_class_skills(); diff --git a/src/db.hpp b/src/db.hpp index ff171163..2d76c2f9 100644 --- a/src/db.hpp +++ b/src/db.hpp @@ -81,6 +81,7 @@ int real_quest(unsigned short vnum); char *fread_string(FILE *fl, const char *error); int vnum_room(char *searchname, CharData *ch); int vnum_zone(char *searchname, CharData *ch); +long get_set_exp(int level, int race, int class_num, int zone); void init_player(CharData *ch); CharData *create_char(void); diff --git a/src/players.cpp b/src/players.cpp index 31e61dbf..8701fbcd 100644 --- a/src/players.cpp +++ b/src/players.cpp @@ -725,6 +725,17 @@ int load_player(const char *name, CharData *ch) { } reset_height_weight(ch); + /* Fix up experience totals to account for level curve change */ + /* This has the beneficial side effect of mitigating the exp */ + /* quirk of the Bard class change */ + if (GET_LEVEL(ch) < LVL_IMMORT) { + long new_exp = adjust_exp_to_level(GET_LEVEL(ch), GET_CLASS(ch), GET_EXP(ch)); + if (new_exp != GET_EXP(ch)) { + log("Adjusting {} (level {:d}) exp from {:d} to {:d} on load", GET_NAME(ch), GET_LEVEL(ch), GET_EXP(ch), new_exp); + GET_EXP(ch) = new_exp; + } + } + fclose(fl); return (id); } @@ -1436,4 +1447,4 @@ void send_save_description(CharData *ch, CharData *dest, bool entering) { } else { log(LogSeverity::Stat, std::max(LVL_IMMORT, GET_INVIS_LEV(ch)), buf); } -} \ No newline at end of file +} diff --git a/src/utils.cpp b/src/utils.cpp index 6f099853..97745520 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -52,8 +52,9 @@ int monk_weight_penalty(CharData *ch) { } static int exp_table[LVL_IMPL + 1]; +static int old_exp_table[LVL_IMPL + 1]; -void init_exp_table(void) { +void init_old_exp_table(void) { long exp, last_exp; int lvl, i; @@ -81,7 +82,7 @@ void init_exp_table(void) { if (lvl < 9) exp = ((lvl * lvl) + lvl) / 2 * 5500; - else if (lvl < 99) { /*(lvl <= LVL_IMMORT) { */ + else if (lvl < 99) { /*(lvl <= LVL_IMMORT) {} */ for (i = 0; lvl >= eval[i].level; ++i) ; --i; @@ -109,11 +110,43 @@ void init_exp_table(void) { else exp = 300000001 + 2 * (lvl - LVL_IMMORT - 1); - exp_table[lvl] = exp; + old_exp_table[lvl] = exp; last_exp = exp; } } +void init_exp_table(void) { + static const long MAX_MORTAL_EXP = 105806000; + long last_exp = 0; + + init_old_exp_table(); + + exp_table[0] = 0; + + for (int lvl = 1; lvl < LVL_IMMORT; lvl++) { + // Base the experience to level up on the experience gained from + // defeating a human rogue mob of the same level. (Chosen because humans + // and rogues have no experience modifier.) + // The factor of 6.615 was chosen to make ** close to MAX_MORTAL_EXP. + long exp = get_set_exp(lvl, RACE_HUMAN, CLASS_ROGUE, -1); + last_exp += long((6.615 * exp) / 100) * 100; + exp_table[lvl] = last_exp; + } + + // Shift everything to make ** exactly equal to MAX_MORTAL_EXP + long adj = exp_table[LVL_IMMORT - 1] - MAX_MORTAL_EXP; + for (int lvl = 1; lvl < LVL_IMMORT; lvl++) { + exp_table[lvl] -= adj; + } + + // Exp for immortal levels is irrelevant, so just pick a big number. + // Levels need to be at least 2 exp apart to avoid breaking things when + // calculating the exp cap for the level. + for (int lvl = LVL_IMMORT; lvl <= LVL_IMPL; lvl++) { + exp_table[lvl] = 300000001 + 2 * (lvl - LVL_IMMORT - 1); + } +} + long exp_next_level(int level, int class_num) { double gain_factor; @@ -131,6 +164,28 @@ long exp_next_level(int level, int class_num) { return exp_table[level] * gain_factor; } +long adjust_exp_to_level(int level, int class_num, long exp) +{ + if (level < 1 || level >= LVL_IMMORT) { + return exp; + } + + long prev_level = exp_next_level(level - 1, class_num); + long next_level = exp_next_level(level, class_num) - 1; + if (exp < prev_level || exp > next_level) { + long old_prev = EXP_GAIN_FACTOR(class_num) * old_exp_table[level - 1]; + long old_next = EXP_GAIN_FACTOR(class_num) * old_exp_table[level] - 1; + if (exp < old_prev) { + return prev_level; + } else if (exp > old_next) { + return next_level; + } + float frac = float(exp - old_prev) / float(old_next - old_prev); + return prev_level + long(frac * (next_level - prev_level)); + } + return exp; +} + /* the "touch" command, essentially. */ int touch(const char *path) { FILE *fl; diff --git a/src/utils.hpp b/src/utils.hpp index f4385f37..d79f9aa2 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -47,6 +47,7 @@ int find_zone(int num); int parse_obj_name(CharData *ch, const char *arg, const char *objname, int numobjs, void *objects, int objsize); void init_flagvectors(); long exp_next_level(int level, int class_num); +long adjust_exp_to_level(int level, int class_num, long exp); void init_exp_table(void); CharData *is_playing(char *vict_name);