diff --git a/src/limits.c b/src/limits.c index 6fe0259..e7f80db 100644 --- a/src/limits.c +++ b/src/limits.c @@ -23,6 +23,7 @@ #include "fight.h" #include "screen.h" #include "mud_event.h" +#include /* local file scope function prototypes */ static int graf(int grafage, int p0, int p1, int p2, int p3, int p4, int p5, int p6); @@ -221,30 +222,65 @@ void run_autowiz(void) #endif /* CIRCLE_UNIX || CIRCLE_WINDOWS */ } +/* Requires: find_skill_num(), GET_WIS(), wis_app[], GET_SKILL(), SET_SKILL(), rand_number() + * Cooldown: 1 hour - 5 * WIS bonus minutes (floored at 0) + * Rolls: failure -> 1..20, success -> 1..100 + * Cap: 90% (change MIN(90, ...) if you want a different cap) + */ void gain_skill(struct char_data *ch, char *skill, bool success) { int skill_num, base, roll, increase; + int wisb, cd_seconds; + time_t now; if (IS_NPC(ch)) return; + /* Resolve index and validate against table size */ skill_num = find_skill_num(skill); - if (skill_num <= 0) + if (skill_num <= 0 || skill_num > MAX_SKILLS) return; - base = GET_SKILL(ch, skill_num); + /* Respect per-skill cooldown: do nothing if still cooling down */ + now = time(0); + if (GET_SKILL_NEXT_GAIN(ch, skill_num) != 0 && + now < GET_SKILL_NEXT_GAIN(ch, skill_num)) { + return; + } - if (success) { /* learning from successes is more difficult */ - roll = rand_number(1, 400); - if (roll >= (400 - (wis_app[GET_WIS(ch)].bonus) * 4)) { - increase = base + 1; - SET_SKILL(ch, skill_num, MIN(90, increase)); - } - } else { /* learning from failures is easier */ + base = GET_SKILL(ch, skill_num); + /* If already capped, bail early (and don’t start cooldown) */ + if (base >= 90) + return; + + /* Wisdom bonus from wis_app[] (constants.c). Higher = better learning & shorter cooldown. */ + wisb = wis_app[GET_WIS(ch)].bonus; + + if (success) { + /* Learning from success is harder: 1..100, threshold scales with WIS */ roll = rand_number(1, 100); - if (roll >= (100 - wis_app[GET_WIS(ch)].bonus)) { + /* Old 1..400 with (400 - wisb*4) ⇒ scaled: (100 - wisb) */ + if (roll >= (100 - wisb)) { increase = base + 1; SET_SKILL(ch, skill_num, MIN(90, increase)); + + /* Cooldown only when an increase actually happens */ + cd_seconds = 3600 - (wisb * 5 * 60); /* 1 hour - 5 * WIS minutes */ + if (cd_seconds < 0) cd_seconds = 0; + GET_SKILL_NEXT_GAIN(ch, skill_num) = now + cd_seconds; + } + } else { + /* Learning from failure is easier: 1..20, threshold scales with WIS */ + roll = rand_number(1, 20); + /* Old 1..100 with (100 - wisb) ⇒ scaled: (20 - wisb) */ + if (roll >= (20 - wisb)) { + increase = base + 1; + SET_SKILL(ch, skill_num, MIN(90, increase)); + + /* Cooldown only when an increase actually happens */ + cd_seconds = 3600 - (wisb * 5 * 60); /* 1 hour - 5 * WIS minutes */ + if (cd_seconds < 0) cd_seconds = 0; + GET_SKILL_NEXT_GAIN(ch, skill_num) = now + cd_seconds; } } } diff --git a/src/players.c b/src/players.c index 68b5b8e..3b7d77b 100644 --- a/src/players.c +++ b/src/players.c @@ -434,6 +434,14 @@ int load_char(const char *name, struct char_data *ch) if (!strcmp(tag, "Sex ")) GET_SEX(ch) = atoi(line); else if (!strcmp(tag, "ScrW")) GET_SCREEN_WIDTH(ch) = atoi(line); else if (!strcmp(tag, "Skil")) load_skills(fl, ch); + else if (!strcmp(tag, "SkGt")) { /* Skill Gain Timers */ + for (int i = 1; i <= MAX_SKILLS; i++) { + long t = 0; + if (fscanf(fl, " %ld", &t) != 1) + t = 0; + GET_SKILL_NEXT_GAIN(ch, i) = (time_t)t; + } + } else if (!strcmp(tag, "Str ")) load_HMVS(ch, line, LOAD_STRENGTH); break; @@ -673,6 +681,12 @@ void save_char(struct char_data * ch) fprintf(fl, "0 0\n"); } + /* Write per-skill next gain times as epoch seconds. */ + fprintf(fl, "SkGt:"); /* Skill Gain Timer */ + for (int i = 1; i <= MAX_SKILLS; i++) + fprintf(fl, " %ld", (long)GET_SKILL_NEXT_GAIN(ch, i)); + fputc('\n', fl); + /* Save affects */ if (tmp_aff[0].spell > 0) { fprintf(fl, "Affs:\n"); diff --git a/src/structs.h b/src/structs.h index 2531560..f233612 100644 --- a/src/structs.h +++ b/src/structs.h @@ -14,6 +14,7 @@ #include "protocol.h" /* Kavir Plugin*/ #include "lists.h" +#include /** If you want equipment to be automatically equipped to the same place * it was when players rented, set the define below to 1 because @@ -968,6 +969,7 @@ 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 */ + time_t next_skill_gain[MAX_SKILLS+1]; /* indexed by skill/spell number */ }; /** Specials needed only by PCs, not NPCs. Space for this structure is diff --git a/src/utils.h b/src/utils.h index 57f5eb0..e23eb56 100644 --- a/src/utils.h +++ b/src/utils.h @@ -130,7 +130,7 @@ void set_title(struct char_data *ch, char *title); void gain_exp(struct char_data *ch, int gain); void gain_exp_regardless(struct char_data *ch, int gain); void gain_condition(struct char_data *ch, int condition, int value); -void gain_skill(struct char_data *ch, char *skill, bool failure); +void gain_skill(struct char_data *ch, char *skill, bool success); void point_update(void); void update_pos(struct char_data *victim); void run_autowiz(void); @@ -617,6 +617,9 @@ do \ #define GET_SKILL(ch, i) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.skills[i])) /** Copy the current skill level i of ch to pct. */ #define SET_SKILL(ch, i, pct) do { CHECK_PLAYER_SPECIAL((ch), (ch)->player_specials->saved.skills[i]) = pct; } while(0) +/** Per-skill next gain time (epoch seconds). Index with a valid skill number. **/ +#define GET_SKILL_NEXT_GAIN(ch, i) \ + (CHECK_PLAYER_SPECIAL((ch), (ch)->player_specials->saved.next_skill_gain[(i)])) /** The player's default sector type when buildwalking */ #define GET_BUILDWALK_SECTOR(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->buildwalk_sector))