From 385f1c1e3e94d0f3e41fb63a76601a7f269368b6 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 14 Dec 2025 13:58:09 +0100 Subject: [PATCH 01/14] =?UTF-8?q?Verzauberungen,=20die=20eigene=20Daten=20?= =?UTF-8?q?speichern,=20m=C3=BCssen=20Speicher=20reservieren,=20in=20den?= =?UTF-8?q?=20sie=20sie=20laden=20(null-pointer=20crash).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/kernel/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernel/version.c b/src/kernel/version.c index 56acac4fb..b017fdb12 100644 --- a/src/kernel/version.c +++ b/src/kernel/version.c @@ -8,7 +8,7 @@ #ifndef ERESSEA_VERSION /* the version number, if it was not passed to make with -D */ -#define ERESSEA_VERSION "30.4.0" +#define ERESSEA_VERSION "31.1.0" #endif const char *eressea_version(void) { From 54c2e6e3e0407230921c71ac6daf18231a99c5a7 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 14 Dec 2025 19:35:35 +0100 Subject: [PATCH 02/14] update announce mailing list after the turn --- process/CMakeLists.txt | 2 +- process/cron/run-eressea.cron | 1 + process/updatelist.sh | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100755 process/updatelist.sh diff --git a/process/CMakeLists.txt b/process/CMakeLists.txt index 106c4c864..7039bf127 100644 --- a/process/CMakeLists.txt +++ b/process/CMakeLists.txt @@ -1,5 +1,5 @@ install(PROGRAMS create-orders backup-box backup-onedrive - run-turn send-zip-report + run-turn send-zip-report updatelist.sh send-bz2-report compress.py compress.sh epasswd.py accept-orders.py getemail.py checkpasswd.py sendreport.sh sendreports.sh orders-accept DESTINATION bin) diff --git a/process/cron/run-eressea.cron b/process/cron/run-eressea.cron index f72d37482..11993132d 100755 --- a/process/cron/run-eressea.cron +++ b/process/cron/run-eressea.cron @@ -86,6 +86,7 @@ backup BACKUP "$GAME" "$TURN" upload "$BACKUP" rm -f execute.lock "$BIN/run-turn" "$GAME" "$TURN" +"$BIN/updatelist.sh" "$TURN" touch execute.lock (( NEXTTURN=TURN+1 )) || true diff --git a/process/updatelist.sh b/process/updatelist.sh new file mode 100755 index 000000000..cf63df8e1 --- /dev/null +++ b/process/updatelist.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +TURN=$1 +LIST=eressea-announce@kn-bremen.de +awk '{ if ($2 =="'$TURN'") print $5 }' deadlog.txt |\ + mailman delmembers -f- -l$LIST +tail reports/reports.txt | cut -d: -f2 | sed -e 's/email=//' | \ + mailman addmembers - $LIST + From 88fceb3a5ce2a4fb7682b2f8a55fafabdd24ecb2 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 11 Jan 2026 18:55:30 +0100 Subject: [PATCH 03/14] fix some minor coverity finds. --- src/bind_region.c | 16 +++++++++------- src/kernel/connection.c | 7 +++++-- src/main.c | 6 +++--- src/summary.c | 16 +++++++++------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/bind_region.c b/src/bind_region.c index 782900075..c80eb7efd 100644 --- a/src/bind_region.c +++ b/src/bind_region.c @@ -273,15 +273,17 @@ static int tolua_region_has_border(lua_State *L) if (btype) { direction_t dir = (direction_t)tolua_tonumber(L, 3, 0); region *r2 = rconnect(r, dir); - connection *b; - for (b = get_borders(r, r2); b; b = b->next) { - if (b->type == btype) { - lua_pushboolean(L, true); - return 1; + if (r2) { + connection *b; + for (b = get_borders(r, r2); b; b = b->next) { + if (b->type == btype) { + lua_pushboolean(L, true); + return 1; + } } + lua_pushboolean(L, false); + return 1; } - lua_pushboolean(L, false); - return 1; } return 0; } diff --git a/src/kernel/connection.c b/src/kernel/connection.c index f5dfd43d1..8a6740c78 100644 --- a/src/kernel/connection.c +++ b/src/kernel/connection.c @@ -100,8 +100,11 @@ static connection **get_borders_i(const region * r1, const region * r2) connection *get_borders(const region * r1, const region * r2) { - connection **bp = get_borders_i(r1, r2); - return *bp; + if (r1 && r2) { + connection **bp = get_borders_i(r1, r2); + return *bp; + } + return NULL; } connection *border_create(region *from) diff --git a/src/main.c b/src/main.c index 9d8973225..a3de84ae9 100644 --- a/src/main.c +++ b/src/main.c @@ -239,7 +239,7 @@ static int parse_args(int argc, char **argv) case 'l': i = get_arg(argc, argv, 2, i, &arg, NULL); if (arg) { - log_flags = arg ? atoi(arg) : 0xff; + log_flags = atoi(arg); } else { return usage(argv[0], NULL); } @@ -263,7 +263,7 @@ static int parse_args(int argc, char **argv) case 'w': i = get_arg(argc, argv, 2, i, &arg, NULL); if (arg) { - bcrypt_workfactor = arg ? atoi(arg) : 0xff; + bcrypt_workfactor = atoi(arg); } else { return usage(argv[0], NULL); } @@ -274,7 +274,7 @@ static int parse_args(int argc, char **argv) case 'v': i = get_arg(argc, argv, 2, i, &arg, NULL); if (arg) { - verbosity = arg ? atoi(arg) : 0xff; + verbosity = atoi(arg); } break; case 'h': diff --git a/src/summary.c b/src/summary.c index b39d0df79..a98f74a5e 100644 --- a/src/summary.c +++ b/src/summary.c @@ -248,7 +248,6 @@ static int cmp_nmr_faction(const void *a, const void *b) static void report_nmrs(FILE *F, int timeout) { nmr_faction *nmrs = NULL; - size_t len, i; faction *f; for (f = factions; f; f = f->next) { if (turn - 1 - f->lastorders > 0) { @@ -257,13 +256,16 @@ static void report_nmrs(FILE *F, int timeout) nmr->nmr = turn - 1 - f->lastorders; } } - len = arrlen(nmrs); - qsort(nmrs, len, sizeof(struct nmr_faction), cmp_nmr_faction); - fprintf(F, "\n\nFactions with NMRs:\n"); - for (i = 0; i != len; ++i) { - out_faction(F, nmrs[i].f); + if (nmrs) { + size_t len, i; + len = arrlen(nmrs); + qsort(nmrs, len, sizeof(struct nmr_faction), cmp_nmr_faction); + fprintf(F, "\n\nFactions with NMRs:\n"); + for (i = 0; i != len; ++i) { + out_faction(F, nmrs[i].f); + } + arrfree(nmrs); } - arrfree(nmrs); } void report_summary(const summary * s, bool full) From fc870252df99ae7e5fe335057f6b2a6460fb94d2 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 25 Jan 2026 18:07:15 +0100 Subject: [PATCH 04/14] fix orcs learning every skill slowly --- src/exparse.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exparse.c b/src/exparse.c index a80067628..4c28b3cfa 100644 --- a/src/exparse.c +++ b/src/exparse.c @@ -1070,7 +1070,7 @@ static void start_races(parseinfo *pi, const XML_Char *el, const XML_Char **attr } else if (xml_strequal(el, "skill")) { const XML_Char *name = NULL; - int i, speed = 0, mod = 0; + int i, speed = INT_MAX, mod = 0; for (i = 0; attr[i]; i += 2) { const XML_Char *key = attr[i], *val = attr[i + 1]; @@ -1091,7 +1091,7 @@ static void start_races(parseinfo *pi, const XML_Char *el, const XML_Char **attr skill_t sk = find_skill(name); if (sk != NOSKILL) { rc->bonus[sk] = (char)mod; - if (speed != 0) { + if (speed != INT_MAX) { set_study_speed(rc, sk, speed); } } From 5c3bb2a64655774c16187daad8fbeb47d63a5bf0 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 25 Jan 2026 18:18:42 +0100 Subject: [PATCH 05/14] INT_MAX needs limits.h --- src/exparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/exparse.c b/src/exparse.c index 4c28b3cfa..9fca11990 100644 --- a/src/exparse.c +++ b/src/exparse.c @@ -28,6 +28,7 @@ #include #include +#include #include #ifdef XML_LARGE_SIZE From 0638080126cba9cc0e2f3ee03cee9dc439188089 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 18 Jan 2026 12:56:02 +0100 Subject: [PATCH 06/14] Apply study_speed when new level is reached. --- res/e3a/races.xml | 2 +- src/crimport.c | 2 +- src/kernel/save.c | 2 +- src/kernel/skills.c | 24 +++++++++++++++++------- src/kernel/skills.h | 9 ++++++--- src/kernel/skills.test.c | 2 +- src/kernel/unit.c | 4 ++-- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/res/e3a/races.xml b/res/e3a/races.xml index ad2e6c1a9..95c46852a 100644 --- a/res/e3a/races.xml +++ b/res/e3a/races.xml @@ -714,7 +714,7 @@ - + diff --git a/src/crimport.c b/src/crimport.c index 13c45d13a..e5f82a268 100644 --- a/src/crimport.c +++ b/src/crimport.c @@ -898,7 +898,7 @@ static enum CR_Error handle_skill(context *ctx, const char *value, const char *n int level = atoi(val + 1) - rc_skillmod(rc, sk) - terrain_mod(rc, sk, u->region); if (level > 0) { struct skill *sv = add_skill(u, sk); - sk_set_level(sv, level); + sk_set_level(u, sv, level); sv->old = sv->level; } if (sk == SK_STAMINA) { diff --git a/src/kernel/save.c b/src/kernel/save.c index 2b54afaba..958ac4039 100644 --- a/src/kernel/save.c +++ b/src/kernel/save.c @@ -383,7 +383,7 @@ static void write_skills(gamedata *data, const unit *u) { #ifndef NDEBUG assert(SK_SKILL(sv) > sk); sk = SK_SKILL(sv); - assert(sv->days <= MAX_DAYS_TO_NEXT_LEVEL(sv->level)); + ASSERT_VALID_SKILL(sv, u_race(u)); #endif WRITE_INT(data->store, sv->id); WRITE_INT(data->store, sv->level); diff --git a/src/kernel/skills.c b/src/kernel/skills.c index 43e75d809..66403fae7 100644 --- a/src/kernel/skills.c +++ b/src/kernel/skills.c @@ -5,6 +5,7 @@ #include "unit.h" #include +#include #include #include @@ -93,16 +94,19 @@ static int progress_weeks(unsigned int level) static void skill_set(skill *sv, unsigned int level, unsigned int days) { - assert(days <= MAX_DAYS_TO_NEXT_LEVEL(level)); sv->level = level; sv->days = days; assert(sv->days == days && sv->level == level); } -void sk_set_level(skill *sv, int level) +void sk_set_level(const struct unit *u, skill *sv, int level) { int weeks = rule_random_progress() ? progress_weeks(level + 1) : (level + 1); - skill_set(sv, level, weeks * SKILL_DAYS_PER_WEEK); + const struct race *rc = u ? u_race(u) : NULL; + int speed = rc ? study_speed(rc, sv->id) : SKILL_DAYS_PER_WEEK; + int days = SKILL_DAYS_PER_WEEK + (weeks - 1) * speed; + ASSERT_VALID_SKILL(sv, rc); + skill_set(sv, level, days); } void increase_skill_weeks(unit * u, enum skill_t sk, const unsigned int weeks) @@ -114,10 +118,10 @@ void increase_skill_weeks(unit * u, enum skill_t sk, const unsigned int weeks) } while (sv->days <= days) { days -= sv->days; - sk_set_level(sv, sv->level + 1); + sk_set_level(u, sv, sv->level + 1); } sv->days -= days; - assert(sv->days <= MAX_DAYS_TO_NEXT_LEVEL(sv->level)); + ASSERT_VALID_SKILL(sv, u_race(u)); } void reduce_skill_weeks(unit * u, skill * sv, const unsigned int weeks) @@ -133,9 +137,9 @@ void reduce_skill_weeks(unit * u, skill * sv, const unsigned int weeks) } if (sv->level == 0) { /* reroll */ - sk_set_level(sv, sv->level + 1); + sk_set_level(u, sv, sv->level + 1); } - assert(sv->days <= MAX_DAYS_TO_NEXT_LEVEL(sv->level)); + ASSERT_VALID_SKILL(sv, u_race(u)); } int skill_compare(const skill * sk, const skill * sc) @@ -232,3 +236,9 @@ int skill_days(unit *u, enum skill_t sk) const skill *sv = unit_skill(u, sk); return sv ? sv->days : 1; } + +int study_speed(const struct race *rc, skill_t sk) +{ + int mod = (rc && rc->study_speed) ? rc->study_speed[sk] : 0; + return SKILL_DAYS_PER_WEEK - mod; +} diff --git a/src/kernel/skills.h b/src/kernel/skills.h index a8c26581e..5e3caddb9 100644 --- a/src/kernel/skills.h +++ b/src/kernel/skills.h @@ -4,7 +4,9 @@ #define MAX_WEEKS_TO_NEXT_LEVEL(level) ((level) * 2 + 1) #define MAX_DAYS_TO_NEXT_LEVEL(level) (SKILL_DAYS_PER_WEEK * MAX_WEEKS_TO_NEXT_LEVEL(level)) - +#define MAX_DAYS_TO_NEXT_LEVEL_EX(level, speed) (SKILL_DAYS_PER_WEEK + (speed) * (MAX_WEEKS_TO_NEXT_LEVEL(level) - 1)) +#define ASSERT_VALID_SKILL(sv, rc) \ + assert((sv)->days <= MAX_DAYS_TO_NEXT_LEVEL_EX((sv)->level, study_speed((rc), (sv)->id))) typedef struct skill { unsigned int id : 5; unsigned int level : 7; @@ -38,11 +40,12 @@ struct attrib *make_skillmod(enum skill_t sk, skillmod_fun special, void increase_skill_weeks(struct unit * u, enum skill_t sk, const unsigned int weeks); void reduce_skill_weeks(struct unit *u, skill * sv, const unsigned int weeks); int merge_skill(const skill* sv, const skill* sn, skill* result, int n, int add); -void sk_set_level(skill * sv, int level); +void sk_set_level(const struct unit *u, skill * sv, int level); int skill_compare(const skill* sk, const skill* sc); int skill_level(struct unit *u, enum skill_t sk); /** number of days-equivalent the unit must STUDY to reach the next level: */ int skill_days(struct unit *u, enum skill_t sk); - +/** number of days in a week for this learner */ +int study_speed(const struct race *rc, enum skill_t sk); #define SK_SKILL(sv) ((skill_t) (sv->id)) diff --git a/src/kernel/skills.test.c b/src/kernel/skills.test.c index d7752c304..9c4d46bfb 100644 --- a/src/kernel/skills.test.c +++ b/src/kernel/skills.test.c @@ -17,7 +17,7 @@ static void test_skill_set(CuTest *tc) test_setup(); config_set_int("study.random_progress", 0); - sk_set_level(&value, 2); + sk_set_level(NULL, &value, 2); CuAssertIntEquals(tc, 1, value.old); CuAssertIntEquals(tc, 2, value.level); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, value.days); diff --git a/src/kernel/unit.c b/src/kernel/unit.c index 2288dffb3..d4842d9df 100644 --- a/src/kernel/unit.c +++ b/src/kernel/unit.c @@ -581,13 +581,13 @@ void set_level(struct unit * u, enum skill_t sk, unsigned int value) for (len = arrlen(u->skills), s = 0; s != len; ++s) { skill* sv = u->skills + s; if (sv->id == sk) { - sk_set_level(sv, value); + sk_set_level(u, sv, value); sv->old = sv->level; return; } ++sv; } - sk_set_level(add_skill(u, sk), value); + sk_set_level(u, add_skill(u, sk), value); } static int leftship_age(struct attrib *a, void *owner) From 585536944074e5836b47fc97761722b93730e87e Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Tue, 20 Jan 2026 21:08:42 +0100 Subject: [PATCH 07/14] increase skills by days, not weeks (WIP) fixes a lot of rounding errors. --- src/battle.c | 1 + src/kernel/skills.c | 67 ++++++++++++++++++++++++-------------- src/kernel/skills.h | 5 +-- src/kernel/skills.test.c | 51 +++++++++++++++++------------ src/laws.c | 2 +- src/spells.c | 2 +- src/study.c | 70 +++++++--------------------------------- src/study.h | 1 - src/triggers/shock.c | 2 +- 9 files changed, 92 insertions(+), 109 deletions(-) diff --git a/src/battle.c b/src/battle.c index 26cad87e7..0804fae7e 100644 --- a/src/battle.c +++ b/src/battle.c @@ -40,6 +40,7 @@ #include "kernel/region.h" #include "kernel/ship.h" #include "kernel/skill.h" +#include "kernel/skills.h" #include "kernel/terrain.h" #include "kernel/unit.h" #include "kernel/spell.h" diff --git a/src/kernel/skills.c b/src/kernel/skills.c index 66403fae7..68989079d 100644 --- a/src/kernel/skills.c +++ b/src/kernel/skills.c @@ -109,37 +109,56 @@ void sk_set_level(const struct unit *u, skill *sv, int level) skill_set(sv, level, days); } -void increase_skill_weeks(unit * u, enum skill_t sk, const unsigned int weeks) -{ - skill *sv = unit_skill(u, sk); - unsigned int days = weeks * SKILL_DAYS_PER_WEEK; - if (!sv) { - sv = add_skill(u, sk); - } - while (sv->days <= days) { - days -= sv->days; - sk_set_level(u, sv, sv->level + 1); +static void increase_skill_days(unit *u, skill *sv, unsigned int days) { + if (days > 0) { + unsigned int leveldays = sv->days; + while (leveldays <= days) { + sk_set_level(u, sv, sv->level + 1); + days -= leveldays; + leveldays = sv->days; + } + sv->days = leveldays - days; + ASSERT_VALID_SKILL(sv, u_race(u)); } - sv->days -= days; - ASSERT_VALID_SKILL(sv, u_race(u)); } -void reduce_skill_weeks(unit * u, skill * sv, const unsigned int weeks) +static void reduce_skill_days(unit *u, skill *sv, unsigned int days) { - unsigned int days = weeks * SKILL_DAYS_PER_WEEK; - unsigned int max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); + if (sv) { + unsigned int max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); + while (sv->days + days > max_days) { + // maximum number of days before we must step down a level: + unsigned int days_lost = max_days - sv->days; + // subtract those days of un-learning, step down a level: + days -= days_lost; + sk_set_level(u, sv, sv->level - 1); + max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); + } + sv->days += days; + } +} - sv->days += days; - while (sv->level > 0 && sv->days > max_days) { - sv->days -= sv->level * SKILL_DAYS_PER_WEEK; - --sv->level; - max_days -= 2 * SKILL_DAYS_PER_WEEK; +void change_skill(unit *u, skill *sv, int days) +{ + assert(sv); + if (days < 0) { + reduce_skill_days(u, sv, -days); + } + else { + increase_skill_days(u, sv, days); } - if (sv->level == 0) { - /* reroll */ - sk_set_level(u, sv, sv->level + 1); +} + +void change_skill_days(struct unit *u, enum skill_t sk, int days) +{ + assert(sk >= 0 && sk < MAXSKILLS); + if (days != 0) { + skill *sv = unit_skill(u, sk); + if (!sv && days > 0) { + sv = add_skill(u, sk); + } + change_skill(u, sv, days); } - ASSERT_VALID_SKILL(sv, u_race(u)); } int skill_compare(const skill * sk, const skill * sc) diff --git a/src/kernel/skills.h b/src/kernel/skills.h index 5e3caddb9..995f55bdb 100644 --- a/src/kernel/skills.h +++ b/src/kernel/skills.h @@ -37,8 +37,8 @@ int skillmod(const struct unit *u, const struct region *r, struct attrib *make_skillmod(enum skill_t sk, skillmod_fun special, double multiplier, int bonus); -void increase_skill_weeks(struct unit * u, enum skill_t sk, const unsigned int weeks); -void reduce_skill_weeks(struct unit *u, skill * sv, const unsigned int weeks); +void change_skill_days(struct unit *u, enum skill_t sk, int days); +void change_skill(struct unit *u, skill *sv, int days); int merge_skill(const skill* sv, const skill* sn, skill* result, int n, int add); void sk_set_level(const struct unit *u, skill * sv, int level); int skill_compare(const skill* sk, const skill* sc); @@ -48,4 +48,5 @@ int skill_level(struct unit *u, enum skill_t sk); int skill_days(struct unit *u, enum skill_t sk); /** number of days in a week for this learner */ int study_speed(const struct race *rc, enum skill_t sk); + #define SK_SKILL(sv) ((skill_t) (sv->id)) diff --git a/src/kernel/skills.test.c b/src/kernel/skills.test.c index 9c4d46bfb..2e5dee73a 100644 --- a/src/kernel/skills.test.c +++ b/src/kernel/skills.test.c @@ -31,54 +31,63 @@ static void test_skill_change(CuTest *tc) test_setup(); config_set_int("study.random_progress", 0); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); - increase_skill_weeks(u, SK_CROSSBOW, 1); + set_number(u, 2); // number should have no effect on skill values + change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); sv = unit_skill(u, SK_CROSSBOW); CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1, sv->level); - CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + /* no random progress, so it will take 2 weeks of learning to next level: */ CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, sv->days); - increase_skill_weeks(u, SK_CROSSBOW, 1); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1, sv->level); - CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, sv->days); - increase_skill_weeks(u, SK_CROSSBOW, 1); + CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 2, sv->level); - CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv->days); - increase_skill_weeks(u, SK_CROSSBOW, 4); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, 4 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3, sv->level); - CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - reduce_skill_weeks(u, sv, 1); + change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3, sv->level); - CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, sv->days); - reduce_skill_weeks(u, sv, 1); + CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3, sv->level); CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, sv->days); - reduce_skill_weeks(u, sv, 2); + change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK * 2); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3, sv->level); - CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, sv->days); - reduce_skill_weeks(u, sv, 1); + CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 2, sv->level); - CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); sv->level = 10; - reduce_skill_weeks(u, sv, 25); - CuAssertIntEquals(tc, 8, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 8, sv->level); - CuAssertIntEquals(tc, 11 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 11 * SKILL_DAYS_PER_WEEK, sv->days); + // to reach level 11: 21 weeks = 630 days + // of which only 4 weeks left + // => we can lose 17 weeks without losing a level + // losing 8 more, though! + // so the level drop sets us to expected time to get to level 10 (10 weeks) + // plus those 8 should make 18 weeks + // 18 weeks is less or equal to what level 10 needs (19) + change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK * 25); + CuAssertIntEquals(tc, 9, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 9, sv->level); + CuAssertIntEquals(tc, 18 * SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 18 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); } static void test_set_level(CuTest * tc) diff --git a/src/laws.c b/src/laws.c index f92063faa..af6fecab7 100644 --- a/src/laws.c +++ b/src/laws.c @@ -189,7 +189,7 @@ static void potion_effects(unit *u) { } /* bestes Talent raussuchen */ if (sb != NULL) { - reduce_skill_weeks(u, sb, weeks); + change_skill(u, sb, -SKILL_DAYS_PER_WEEK * weeks); ADDMSG(&u->faction->msgs, msg_message("dumbeffect", "unit weeks skill", u, weeks, (skill_t)sb->id)); } /* sonst Glueck gehabt: wer nix weiss, kann nix vergessen... */ diff --git a/src/spells.c b/src/spells.c index b468a3b77..756d89c84 100644 --- a/src/spells.c +++ b/src/spells.c @@ -4051,7 +4051,7 @@ static int sp_headache(castorder * co) int change = target->number; if (change > 10) change = 10; change *= (rng_uint() % 2 + 1) / target->number; - reduce_skill_weeks(target, smax, change); + change_skill(target, smax, -SKILL_DAYS_PER_WEEK * change); } set_order(&target->thisorder, NULL); diff --git a/src/study.c b/src/study.c index 6560e7add..05f9cc907 100644 --- a/src/study.c +++ b/src/study.c @@ -687,64 +687,24 @@ bool can_teach(const unit* u) return !(fval(u, UFL_WERE) || fval(u_race(u), RCF_NOTEACH)); } -static learn_fun inject_learn_fun = 0; - -void inject_learn(learn_fun fun) { - inject_learn_fun = fun; -} - -static void increase_skill_days(unit *u, skill_t sk, int days) { - assert(sk >= 0 && sk < MAXSKILLS && days >= 0); - if (days > 0) { - int leveldays = SKILL_DAYS_PER_WEEK * u->number; - int weeks = 0; - if (inject_learn_fun) { - inject_learn_fun(u, sk, days); - } - while (days >= leveldays) { - ++weeks; - days -= leveldays; - } - if (days > 0 && rng_int() % leveldays >= leveldays - days) { - ++weeks; - } - if (weeks > 0) { - increase_skill_weeks(u, sk, weeks); - } - } -} - void produceexp(struct unit *u, enum skill_t sk) { assert(u); if (u->number > 0) { const struct race *rc = u_race(u); if ((rc->flags & RCF_NOLEARN) == 0 && rc_can_learn(rc, sk)) { - increase_skill_days(u, sk, produceexp_days() * u->number); + change_skill_days(u, sk, produceexp_days() * u->number); } } } -static void reduce_skill_days(unit *u, skill_t sk, int days) { - if (days > 0) { - skill *sv = unit_skill(u, sk); - if (sv) { - while (days > 0) { - if (days >= SKILL_DAYS_PER_WEEK * u->number) { - reduce_skill_weeks(u, sv, 1); - days -= SKILL_DAYS_PER_WEEK; - } - else { - if (chance(days / ((double)SKILL_DAYS_PER_WEEK * u->number))) /* (rng_int() % (30 * u->number) < days)*/ - reduce_skill_weeks(u, sv, 1); - days = 0; - } - } - } - } +static learn_fun inject_learn_fun = 0; + +void inject_learn(learn_fun fun) { + inject_learn_fun = fun; } -/** +/** * days should be scaled by u->number; SKILL_DAYS_PER_WEEK * u->number is one week worth of learning * @return int * The additional spend, i.e. from an academy. @@ -753,6 +713,9 @@ int learn_skill(unit *u, enum skill_t sk, int days, int studycost) { region *r = u->region; int cost = 0; + if (inject_learn_fun) { + inject_learn_fun(u, sk, days); + } if (r->buildings) { static const building_type *bt_artacademy; static const building_type *bt_academy; @@ -809,19 +772,10 @@ int learn_skill(unit *u, enum skill_t sk, int days, int studycost) { if (fval(u, UFL_HUNGER)) { days /= 2; } - change_skill_days(u, sk, days); + change_skill_days(u, sk, days / u->number); return cost; } -void change_skill_days(unit *u, enum skill_t sk, int days) { - if (days < 0) { - reduce_skill_days(u, sk, -days); - } - else { - increase_skill_days(u, sk, days); - } -} - /** * Talente von Daemonen verschieben sich. */ @@ -864,14 +818,14 @@ void demon_skillchange(unit *u) } if (roll < downchance) { - reduce_skill_weeks(u, sv, weeks); + change_skill(u, sv, -SKILL_DAYS_PER_WEEK * weeks); if (sv->level < 1) { /* demons should never forget below 1 */ set_level(u, sv->id, 1); } } else { - change_skill_days(u, sv->id, SKILL_DAYS_PER_WEEK * u->number * weeks); + change_skill_days(u, sv->id, SKILL_DAYS_PER_WEEK * weeks); } } ++sv; diff --git a/src/study.h b/src/study.h index e0ff4d93a..6e8b5c5ba 100644 --- a/src/study.h +++ b/src/study.h @@ -36,7 +36,6 @@ bool check_student(const struct unit *u, struct order *ord, int learn_skill(struct unit *u, enum skill_t sk, int days, int studycost); -void change_skill_days(struct unit *u, enum skill_t sk, int days); void produceexp(struct unit *u, enum skill_t sk); diff --git a/src/triggers/shock.c b/src/triggers/shock.c index 3af2122b7..b41bf29e4 100644 --- a/src/triggers/shock.c +++ b/src/triggers/shock.c @@ -64,7 +64,7 @@ static void do_shock(unit * u, const char *reason) skill* sv = u->skills + s; int weeks = (sv->level * sv->level - sv->level) / 2; int change = (weeks + 9) / 10; - reduce_skill_weeks(u, sv, change); + change_skill(u, sv, -SKILL_DAYS_PER_WEEK * change); } } /* Dies ist ein Hack, um das skillmod und familiar-Attribut beim Mage From f51314bf76ac320886220afb0fa6a8cb3a480f70 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 25 Jan 2026 16:13:29 +0100 Subject: [PATCH 08/14] fix tests related to studying, remove the inject_learn logic --- src/exparse.c | 2 +- src/kernel/race.c | 2 +- src/study.c | 33 +----- src/study.h | 3 - src/study.test.c | 283 +++++++++++++++++++++++----------------------- src/tests.c | 1 + 6 files changed, 147 insertions(+), 177 deletions(-) diff --git a/src/exparse.c b/src/exparse.c index 9fca11990..2a8b17b3d 100644 --- a/src/exparse.c +++ b/src/exparse.c @@ -1091,7 +1091,7 @@ static void start_races(parseinfo *pi, const XML_Char *el, const XML_Char **attr if (name) { skill_t sk = find_skill(name); if (sk != NOSKILL) { - rc->bonus[sk] = (char)mod; + rc->bonus[sk] = (signed char)mod; if (speed != INT_MAX) { set_study_speed(rc, sk, speed); } diff --git a/src/kernel/race.c b/src/kernel/race.c index bda9bceff..9c9f011a5 100644 --- a/src/kernel/race.c +++ b/src/kernel/race.c @@ -455,7 +455,7 @@ void set_study_speed(race *rc, skill_t sk, int modifier) { rc->study_speed = calloc(1, MAXSKILLS); if (!rc->study_speed) abort(); } - rc->study_speed[sk] = (char)modifier; + rc->study_speed[sk] = (signed char)modifier; } const race *rc_otherrace(const race *rc) diff --git a/src/study.c b/src/study.c index 05f9cc907..552731e35 100644 --- a/src/study.c +++ b/src/study.c @@ -164,21 +164,6 @@ static int produceexp_days(void) { return rule; } -static int study_days(unit * u, skill_t sk) -{ - int speed = SKILL_DAYS_PER_WEEK; - if (u_race(u)->study_speed) { - speed += u_race(u)->study_speed[sk]; - if (speed < SKILL_DAYS_PER_WEEK) { - skill *sv = unit_skill(u, sk); - if (sv == NULL) { - speed = SKILL_DAYS_PER_WEEK; - } - } - } - return u->number * speed; -} - static int teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk, bool report, int *academy_students) @@ -215,7 +200,7 @@ teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk, const struct building_type *btype = bt_find("academy"); if (active_building(student, btype)) { /* Jeder Schueler zusaetzlich +10 Tage wenn in Uni. */ - teach->days += students * produceexp_days(); /* learning erhoehen */ + teach->days += students * SKILL_DAYS_PER_WEEK/3; /* learning erhoehen */ /* Lehrer zusaetzlich +1 Tag pro Schueler. */ if (academy_students) { *academy_students += students; @@ -429,7 +414,8 @@ int teach_cmd(unit * teacher, struct order *ord) free_order(new_order); /* parse_order & set_order have each increased the refcount */ } if (academy_students > 0 && sk_academy != NOSKILL) { - change_skill_days(teacher, sk_academy, academy_students); + // TODO: rounding errors here. + change_skill_days(teacher, sk_academy, academy_students / teacher->number); } reset_order(); return 0; @@ -636,7 +622,7 @@ int study_cmd(unit * u, order * ord) } days = teach ? teach->days : 0; - days += study_days(u, sk); + days += SKILL_DAYS_PER_WEEK * u->number; if (studycost) { int cost = studycost * u->number; @@ -698,12 +684,6 @@ void produceexp(struct unit *u, enum skill_t sk) } } -static learn_fun inject_learn_fun = 0; - -void inject_learn(learn_fun fun) { - inject_learn_fun = fun; -} - /** * days should be scaled by u->number; SKILL_DAYS_PER_WEEK * u->number is one week worth of learning * @return int @@ -713,9 +693,6 @@ int learn_skill(unit *u, enum skill_t sk, int days, int studycost) { region *r = u->region; int cost = 0; - if (inject_learn_fun) { - inject_learn_fun(u, sk, days); - } if (r->buildings) { static const building_type *bt_artacademy; static const building_type *bt_academy; @@ -737,7 +714,7 @@ int learn_skill(unit *u, enum skill_t sk, int days, int studycost) { n = n * avail / cost; cost = n * studycost; } - days += produceexp_days() * n; + days += SKILL_DAYS_PER_WEEK * n / 3; } /* the artacademy currently improves the learning of entertainment diff --git a/src/study.h b/src/study.h index 6e8b5c5ba..06619e320 100644 --- a/src/study.h +++ b/src/study.h @@ -41,7 +41,4 @@ void produceexp(struct unit *u, enum skill_t sk); void demon_skillchange(struct unit *u); -typedef void(*learn_fun)(struct unit *u, enum skill_t sk, int days); -void inject_learn(learn_fun fun); - #endif diff --git a/src/study.test.c b/src/study.test.c index 3d06bb248..089354dd4 100644 --- a/src/study.test.c +++ b/src/study.test.c @@ -33,34 +33,6 @@ struct locale; -#define MAXLOG 4 -typedef struct log_entry { - unit *u; - skill_t sk; - int days; -} log_entry; - -static log_entry log_learners[MAXLOG]; -static int log_size; - -static void log_learn(unit *u, skill_t sk, int days) { - if (log_size < MAXLOG) { - log_entry * entry = &log_learners[log_size++]; - entry->u = u; - entry->sk = sk; - entry->days = days; - } -} - -void learn_inject(void) { - log_size = 0; - inject_learn(log_learn); -} - -void learn_reset(void) { - inject_learn(0); -} - typedef struct { unit *u; unit *teachers[2]; @@ -68,6 +40,7 @@ typedef struct { static void setup_study(void) { test_setup(); + config_set("study.random_progress", "0"); mt_create_error(77); mt_create_error(771); mt_create_error(178); @@ -99,7 +72,6 @@ static void setup_teacher(study_fixture *fix, skill_t sk) { assert(fix); setup_study(); - config_set("study.random_progress", "0"); r = test_create_plain(0, 0); f = test_create_faction(); f->locale = lang = test_create_locale(); @@ -185,42 +157,39 @@ static void test_study_speed(CuTest *tc) { race *rc; skill *sv; - test_setup(); - learn_inject(); + setup_study(); rc = test_create_race("orc"); - u = test_create_unit(test_create_faction_ex(rc, NULL), test_create_plain(0, 0)); - set_level(u, SK_BUILDING, 1); - set_level(u, SK_CATAPULT, 1); set_study_speed(rc, SK_BUILDING, -5); - sv = unit_skill(u, SK_BUILDING); - sv->days = 1 * SKILL_DAYS_PER_WEEK; CuAssertIntEquals(tc, -5, rc->study_speed[SK_BUILDING]); - u->thisorder = create_order(K_STUDY, u->faction->locale, skillnames[SK_BUILDING]); - random_source_inject_constants(0.0, 0); - study_cmd(u, u->thisorder); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_BUILDING, log_learners[0].sk); - CuAssertIntEquals(tc, 25, log_learners[0].days); + u = test_create_unit(test_create_faction_ex(rc, NULL), test_create_plain(0, 0)); + + sv = add_skill(u, SK_CATAPULT); + set_level(u, SK_CATAPULT, 3); + CuAssertIntEquals(tc, 3, sv->level); + CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, sv->days); + + sv = add_skill(u, SK_BUILDING); + set_level(u, SK_BUILDING, 1); CuAssertIntEquals(tc, 1, sv->level); - CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK + sv->level * 5, sv->days); + set_level(u, SK_BUILDING, 3); + CuAssertIntEquals(tc, 3, sv->level); + CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK + sv->level * 5, sv->days); - random_source_inject_constants(0.0, 5); - u->flags &= ~UFL_LONGACTION; + // learning at 30 days/week, no study_cost effect here: + u->thisorder = create_order(K_STUDY, u->faction->locale, skillnames[SK_BUILDING]); study_cmd(u, u->thisorder); - CuAssertPtrEquals(tc, u, log_learners[1].u); - CuAssertIntEquals(tc, SK_BUILDING, log_learners[1].sk); - CuAssertIntEquals(tc, 25, log_learners[1].days); - CuAssertIntEquals(tc, 2, sv->level); + CuAssertIntEquals(tc, 3, sv->level); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK + sv->level * 5, sv->days); + // learning at 30 days/week, no study_cost effect here: free_order(u->thisorder); u->thisorder = create_order(K_STUDY, u->faction->locale, skillnames[SK_CATAPULT]); u->flags &= ~UFL_LONGACTION; study_cmd(u, u->thisorder); - CuAssertPtrEquals(tc, u, log_learners[2].u); - CuAssertIntEquals(tc, SK_CATAPULT, log_learners[2].sk); - CuAssertIntEquals(tc, 30, log_learners[2].days); + CuAssertIntEquals(tc, 3, sv->level); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK + sv->level * 5, sv->days); - learn_reset(); test_teardown(); } @@ -254,6 +223,7 @@ static void test_study_bug_2194(CuTest *tc) { unit *u, *u1, *u2; struct locale * loc; building * b; + skill *sv; setup_study(); random_source_inject_constant(0.0); @@ -278,20 +248,16 @@ static void test_study_bug_2194(CuTest *tc) { u_set_building(u2, b); i_change(&u1->items, get_resourcetype(R_SILVER)->itype, 50); i_change(&u2->items, get_resourcetype(R_SILVER)->itype, 50); - learn_inject(); teach_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, 1, log_size); - CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "teach_asgood")); + CuAssertPtrEquals(tc, NULL, unit_skill(u, SK_MAGIC)); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, TEACHDIFFERENCE, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 3 - (u1->number / u->number), sv->days); free_order(u->thisorder); u->thisorder = create_order(K_TEACH, loc, itoa36(u2->no)); - learn_inject(); teach_cmd(u, u->thisorder); - learn_reset(); - CuAssertIntEquals(tc, 0, log_size); + CuAssertPtrEquals(tc, NULL, unit_skill(u, SK_MAGIC)); test_teardown(); } @@ -300,13 +266,17 @@ static void test_academy_building(CuTest *tc) { struct locale * loc; building * b; message * msg; + skill *sv; + const attrib *a; + const teaching_info *ti; + const struct item_type *it_silver; setup_study(); mt_create_va(mt_new("teach_asgood", NULL), "unit:unit", "region:region", "command:order", "student:unit", MT_NEW_END); - random_source_inject_constant(0.0); init_resources(); + it_silver = test_create_silver(); loc = test_create_locale(); setup_locale(loc); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); @@ -326,18 +296,42 @@ static void test_academy_building(CuTest *tc) { u_set_building(u, b); u_set_building(u1, b); u_set_building(u2, b); - i_change(&u1->items, get_resourcetype(R_SILVER)->itype, 50); - i_change(&u2->items, get_resourcetype(R_SILVER)->itype, 50); - learn_inject(); + i_change(&u1->items, it_silver, 50 * u1->number + 10); + i_change(&u2->items, it_silver, 50 * u2->number + 10); + + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv ? sv->days : 0); teach_cmd(u, u->thisorder); - learn_reset(); + // cannot teach u2, because skill too high: CuAssertPtrNotNull(tc, msg = test_find_messagetype(u->faction->msgs, "teach_asgood")); CuAssertPtrEquals(tc, u, (unit *)msg->parameters[0].v); CuAssertPtrEquals(tc, u2, (unit *)msg->parameters[3].v); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, u1->number, log_learners[0].days); + // teacher teaches 15 students, gets 15/2 XP: + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK - (u1->number / u->number), sv->days); + + // u1 gets taught, gets teacher-in-academy bonus: + CuAssertPtrNotNull(tc, a = a_find(u1->attribs, &at_learning)); + ti = (const teaching_info *)a->data.v; + CuAssertIntEquals(tc, u1->number, ti->students); + CuAssertIntEquals(tc, u1->number * 20 * 2, ti->days); + + // u1 is learning with a teacher, gains 60*4/3 = 80 XP: + study_cmd(u1, u1->thisorder); + CuAssertIntEquals(tc, 10, i_get(u1->items, it_silver)); + CuAssertPtrNotNull(tc, sv = unit_skill(u1, SK_CROSSBOW)); + // uses 30 to reach L1, 50 towards L2 (10 remain): + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 10, sv->days); + + // u2 is learning without a teacher, gets to level 1, +10 XP for academy: + study_cmd(u2, u2->thisorder); + CuAssertIntEquals(tc, 10, i_get(u2->items, it_silver)); + CuAssertPtrNotNull(tc, sv = unit_skill(u2, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 20, sv->days); + test_teardown(); } @@ -350,10 +344,9 @@ static void test_academy_bonus(CuTest *tc) { unit *u, *u0, *u1, *u3; struct locale * loc; building * b; + skill *sv; setup_study(); - - random_source_inject_constant(0.0); init_resources(); loc = test_create_locale(); setup_locale(loc); @@ -380,26 +373,35 @@ static void test_academy_bonus(CuTest *tc) { scale_number(u, 2); scale_number(u1, 9); - scale_number(u3, 2); + scale_number(u3, 3); i_change(&u1->items, get_resourcetype(R_SILVER)->itype, 5000); - learn_inject(); + // u0 teaches 10 people, gains +10 XP + sv = unit_skill(u0, SK_CROSSBOW); + CuAssertIntEquals(tc, TEACHDIFFERENCE, sv->level); + CuAssertIntEquals(tc, (sv->level + 1) * SKILL_DAYS_PER_WEEK, sv->days); teach_cmd(u0, u0->thisorder); + CuAssertIntEquals(tc, TEACHDIFFERENCE, sv->level); + CuAssertIntEquals(tc, (sv->level + 1) * SKILL_DAYS_PER_WEEK - SKILL_DAYS_PER_WEEK / 3, sv->days); + + // u teaches remaining 2 person, gains +1 XP + sv = unit_skill(u, SK_CROSSBOW); + CuAssertIntEquals(tc, TEACHDIFFERENCE, sv->level); + CuAssertIntEquals(tc, (sv->level + 1) * SKILL_DAYS_PER_WEEK, sv->days); teach_cmd(u, u->thisorder); + CuAssertIntEquals(tc, TEACHDIFFERENCE, sv->level); + CuAssertIntEquals(tc, (sv->level + 1) * SKILL_DAYS_PER_WEEK - SKILL_DAYS_PER_WEEK / 30, sv->days); + + // u1 lernt in einer Akademie (+40 XP), mit Lehrer (+20): + sv = add_skill(u1, SK_CROSSBOW); study_cmd(u1, u1->thisorder); - study_cmd(u3, u3->thisorder); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK / 3, sv->days); - CuAssertIntEquals(tc, 4, log_size); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertPtrEquals(tc, u0, log_learners[0].u); - CuAssertIntEquals(tc, 10, log_learners[0].days); - CuAssertPtrEquals(tc, u, log_learners[1].u); - CuAssertIntEquals(tc, 1, log_learners[1].days); - CuAssertPtrEquals(tc, u1, log_learners[2].u); - CuAssertIntEquals(tc, 720, log_learners[2].days); - CuAssertPtrEquals(tc, u3, log_learners[3].u); - CuAssertIntEquals(tc, 160, log_learners[3].days); - learn_reset(); + sv = add_skill(u3, SK_CROSSBOW); + study_cmd(u3, u3->thisorder); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK / 3, sv->days); test_teardown(); } @@ -408,7 +410,6 @@ void test_learn_skill_single(CuTest *tc) { skill *sv; setup_study(); - config_set("study.random_progress", "0"); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, SKILL_DAYS_PER_WEEK, 0)); CuAssertPtrNotNull(tc, sv = u->skills); @@ -428,7 +429,6 @@ void test_learn_skill_multi(CuTest *tc) { skill *sv; setup_study(); - config_set("study.random_progress", "0"); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); scale_number(u, 10); CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, SKILL_DAYS_PER_WEEK * u->number, 0)); @@ -532,17 +532,16 @@ static void test_demon_skillchange_hungry(CuTest *tc) { static void test_study_cmd(CuTest *tc) { unit *u; + skill *sv; setup_study(); init_resources(); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); u->thisorder = create_order(K_STUDY, u->faction->locale, "CROSSBOW"); - learn_inject(); study_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, log_learners[0].days); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, sv->days); test_teardown(); } @@ -606,6 +605,7 @@ static void test_study_cost_magic(CuTest *tc) { static void test_study_cost(CuTest *tc) { unit *u; const struct item_type *itype; + skill *sv; setup_study(); @@ -619,13 +619,11 @@ static void test_study_cost(CuTest *tc) { CuAssertIntEquals(tc, 50, study_cost(u, SK_ALCHEMY)); i_change(&u->items, itype, u->number * study_cost(u, SK_ALCHEMY)); - learn_inject(); study_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_ALCHEMY, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * u->number, log_learners[0].days); CuAssertIntEquals(tc, 0, i_get(u->items, itype)); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_ALCHEMY)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, sv->days); test_teardown(); } @@ -633,6 +631,7 @@ static void test_teach_magic(CuTest *tc) { unit *u, *ut; faction *f; const struct item_type *itype; + skill *sv; setup_study(); init_resources(); @@ -646,20 +645,18 @@ static void test_teach_magic(CuTest *tc) { set_level(ut, SK_MAGIC, TEACHDIFFERENCE); create_mage(ut, M_GWYRRD); ut->thisorder = create_order(K_TEACH, f->locale, itoa36(u->no)); - learn_inject(); teach_cmd(ut, ut->thisorder); study_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_MAGIC, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2, log_learners[0].days); CuAssertIntEquals(tc, 0, i_get(u->items, itype)); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_MAGIC)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); test_teardown(); } static void test_teach_cmd(CuTest *tc) { unit *u, *ut; - + setup_study(); init_resources(); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); @@ -668,13 +665,11 @@ static void test_teach_cmd(CuTest *tc) { ut = test_create_unit(u->faction, u->region); set_level(ut, SK_CROSSBOW, TEACHDIFFERENCE); ut->thisorder = create_order(K_TEACH, u->faction->locale, itoa36(u->no)); - learn_inject(); + teach_cmd(ut, ut->thisorder); study_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2 * u->number, log_learners[0].days); + CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); test_teardown(); } @@ -700,7 +695,8 @@ static void test_teach_not_found(CuTest *tc) { static void test_teach_two(CuTest *tc) { unit *u1, *u2, *ut; - + skill *sv; + setup_study(); init_resources(); u1 = test_create_unit(test_create_faction(), test_create_plain(0, 0)); @@ -712,17 +708,17 @@ static void test_teach_two(CuTest *tc) { ut = test_create_unit(u1->faction, u1->region); set_level(ut, SK_CROSSBOW, TEACHDIFFERENCE); ut->thisorder = create_order(K_TEACH, ut->faction->locale, "%s %s", itoa36(u1->no), itoa36(u2->no)); - learn_inject(); teach_cmd(ut, ut->thisorder); study_cmd(u1, u1->thisorder); study_cmd(u2, u2->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u1, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2 * u1->number, log_learners[0].days); - CuAssertPtrEquals(tc, u2, log_learners[1].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[1].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2 * u2->number, log_learners[1].days); + CuAssertPtrNotNull(tc, sv = unit_skill(u1, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); + + CuAssertPtrNotNull(tc, sv = unit_skill(u2, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); + test_teardown(); } @@ -730,6 +726,7 @@ static void test_teach_two_skills(CuTest *tc) { unit *u1, *u2, *ut; faction *f; region *r; + skill *sv; setup_study(); init_resources(); @@ -745,22 +742,22 @@ static void test_teach_two_skills(CuTest *tc) { set_level(ut, SK_ENTERTAINMENT, TEACHDIFFERENCE); set_level(ut, SK_CROSSBOW, TEACHDIFFERENCE); ut->thisorder = create_order(K_TEACH, f->locale, "%s %s", itoa36(u1->no), itoa36(u2->no)); - learn_inject(); teach_cmd(ut, ut->thisorder); study_cmd(u1, u1->thisorder); study_cmd(u2, u2->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u1, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2 * u1->number, log_learners[0].days); - CuAssertPtrEquals(tc, u2, log_learners[1].u); - CuAssertIntEquals(tc, SK_ENTERTAINMENT, log_learners[1].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2 * u2->number, log_learners[1].days); + CuAssertPtrNotNull(tc, sv = unit_skill(u1, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); + + CuAssertPtrNotNull(tc, sv = unit_skill(u2, SK_ENTERTAINMENT)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); test_teardown(); } static void test_teach_one_to_many(CuTest *tc) { unit *u, *ut; + const skill *sv; setup_study(); init_resources(); @@ -770,18 +767,17 @@ static void test_teach_one_to_many(CuTest *tc) { ut = test_create_unit(u->faction, u->region); set_level(ut, SK_CROSSBOW, TEACHDIFFERENCE); ut->thisorder = create_order(K_TEACH, u->faction->locale, itoa36(u->no)); - learn_inject(); teach_cmd(ut, ut->thisorder); study_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 10 + SKILL_DAYS_PER_WEEK * u->number, log_learners[0].days); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK / 2, sv->days); test_teardown(); } static void test_teach_many_to_one(CuTest *tc) { unit *u, *u1, *u2; + const skill *sv; setup_study(); init_resources(); @@ -794,14 +790,14 @@ static void test_teach_many_to_one(CuTest *tc) { u2 = test_create_unit(u->faction, u->region); set_level(u2, SK_CROSSBOW, TEACHDIFFERENCE); u2->thisorder = create_order(K_TEACH, u->faction->locale, itoa36(u->no)); - learn_inject(); + teach_cmd(u1, u1->thisorder); teach_cmd(u2, u2->thisorder); study_cmd(u, u->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, u, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK * u->number, log_learners[0].days); + + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); test_teardown(); } @@ -867,18 +863,17 @@ static void test_teach_many_to_many(CuTest *tc) { scale_number(t2, 2); set_level(t2, SK_CROSSBOW, TEACHDIFFERENCE); t2->thisorder = create_order(K_TEACH, f->locale, "%s %s", itoa36(s1->no), itoa36(s2->no)); - learn_inject(); + teach_cmd(t1, t1->thisorder); teach_cmd(t2, t2->thisorder); study_cmd(s1, s1->thisorder); study_cmd(s2, s2->thisorder); - learn_reset(); - CuAssertPtrEquals(tc, s1, log_learners[0].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[0].sk); - CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK * s1->number, log_learners[0].days); - CuAssertPtrEquals(tc, s2, log_learners[1].u); - CuAssertIntEquals(tc, SK_CROSSBOW, log_learners[1].sk); - CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK * s2->number, log_learners[1].days); + + CuAssertIntEquals(tc, 1, skill_level(s1, SK_CROSSBOW)); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, skill_days(s1, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, skill_level(s2, SK_CROSSBOW)); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, skill_days(s2, SK_CROSSBOW)); + test_teardown(); } diff --git a/src/tests.c b/src/tests.c index fe613943a..0ba65a832 100644 --- a/src/tests.c +++ b/src/tests.c @@ -270,6 +270,7 @@ void test_reset(void) if (month_season == test_months) { month_season = NULL; } + random_source_reset(); free_gamedata(); } From 995c2b09451fec59614d46e61bbf7b06f7e2ad5b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 25 Jan 2026 18:02:24 +0100 Subject: [PATCH 09/14] fix intermittent rustshield test use rng_uint where it makes sense --- src/battle.c | 2 +- src/battle.test.c | 1 + src/kernel/region.c | 4 ++-- src/kernel/unit.c | 2 +- src/util/rand.c | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/battle.c b/src/battle.c index 0804fae7e..d37d846cc 100644 --- a/src/battle.c +++ b/src/battle.c @@ -939,7 +939,7 @@ void kill_troop(troop dt) */ void drain_exp(struct unit *u, int n) { - skill_t sk = (skill_t)(rng_int() % MAXSKILLS); + skill_t sk = (skill_t)(rng_uint() % MAXSKILLS); skill_t ssk; /* TODO (enno): we can use u->skill_size to find a random skill */ diff --git a/src/battle.test.c b/src/battle.test.c index 95b014ceb..9ba14b10e 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -1520,6 +1520,7 @@ static void test_combat_rosthauch(CuTest *tc) { const resource_type *rtype; test_setup(); + random_source_inject_constants(1.0, 0xdeadbeef); init_resources(); rtype = rt_get_or_create("iron"); it_rust1 = create_weapon("sword", rtype); diff --git a/src/kernel/region.c b/src/kernel/region.c index 022c7e3e4..5b5ceee01 100644 --- a/src/kernel/region.c +++ b/src/kernel/region.c @@ -1097,8 +1097,8 @@ void init_region(region *r) t_plain = get_terrain(terrainnames[T_PLAIN]); } if (terrain->size>0) { - horses = rng_int() % (terrain->size / 50); - trees = terrain->size * (30 + rng_int() % 40) / 1000; + horses = rng_uint() % (terrain->size / 50); + trees = terrain->size * (30 + rng_uint() % 40) / 1000; } if (t_plain && terrain == t_plain) { rsethorses(r, horses); diff --git a/src/kernel/unit.c b/src/kernel/unit.c index d4842d9df..33f6ab24d 100644 --- a/src/kernel/unit.c +++ b/src/kernel/unit.c @@ -1231,7 +1231,7 @@ static int newunitid(void) { int random_unit_no; int start_random_no; - random_unit_no = 1 + (rng_int() % MAX_UNIT_NR); + random_unit_no = 1 + (rng_uint() % MAX_UNIT_NR); start_random_no = random_unit_no; while (ufindhash(random_unit_no) || dfindhash(random_unit_no) diff --git a/src/util/rand.c b/src/util/rand.c index cac84bc2a..8a70566fe 100644 --- a/src/util/rand.c +++ b/src/util/rand.c @@ -13,7 +13,7 @@ int lovar(double xpct_x2) int n = (int)(xpct_x2 * 500) + 1; if (n == 0) return 0; - return (rng_int() % n + rng_int() % n) / 1000; + return (rng_uint() % n + rng_uint() % n) / 1000; } /* gaussian distribution From aada53e75bba7e8b36c0f64197c2fab46c0f8661 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 25 Jan 2026 20:31:58 +0100 Subject: [PATCH 10/14] reduce skill is broken (WIP) --- src/kernel/skills.c | 20 +++++++++++++++----- src/kernel/skills.h | 2 +- src/kernel/skills.test.c | 27 +++++++++++++++++++++++++++ src/study.c | 2 +- src/study.test.c | 16 ++++++++++++++++ 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/kernel/skills.c b/src/kernel/skills.c index 68989079d..07aa20ebc 100644 --- a/src/kernel/skills.c +++ b/src/kernel/skills.c @@ -87,7 +87,7 @@ static int progress_weeks(unsigned int level) unsigned int coins = MAX_WEEKS_TO_NEXT_LEVEL(level - 1) - 1; int heads = 1; while (coins--) { - heads += rng_int() % 2; + heads += rng_uint() % 2; } return heads; } @@ -99,7 +99,7 @@ static void skill_set(skill *sv, unsigned int level, unsigned int days) assert(sv->days == days && sv->level == level); } -void sk_set_level(const struct unit *u, skill *sv, int level) +void sk_set_level(const struct unit *u, skill *sv, unsigned int level) { int weeks = rule_random_progress() ? progress_weeks(level + 1) : (level + 1); const struct race *rc = u ? u_race(u) : NULL; @@ -131,10 +131,20 @@ static void reduce_skill_days(unit *u, skill *sv, unsigned int days) unsigned int days_lost = max_days - sv->days; // subtract those days of un-learning, step down a level: days -= days_lost; - sk_set_level(u, sv, sv->level - 1); - max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); + if (sv->level > 0) { + sk_set_level(u, sv, sv->level - 1); + sv->days = 0; + max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); + } + else { + remove_skill(u, (skill_t)sv->id); + sv = NULL; + break; + } + } + if (sv) { + sv->days += days; } - sv->days += days; } } diff --git a/src/kernel/skills.h b/src/kernel/skills.h index 995f55bdb..29ad2c356 100644 --- a/src/kernel/skills.h +++ b/src/kernel/skills.h @@ -40,7 +40,7 @@ struct attrib *make_skillmod(enum skill_t sk, skillmod_fun special, void change_skill_days(struct unit *u, enum skill_t sk, int days); void change_skill(struct unit *u, skill *sv, int days); int merge_skill(const skill* sv, const skill* sn, skill* result, int n, int add); -void sk_set_level(const struct unit *u, skill * sv, int level); +void sk_set_level(const struct unit *u, skill * sv, unsigned int level); int skill_compare(const skill* sk, const skill* sc); int skill_level(struct unit *u, enum skill_t sk); diff --git a/src/kernel/skills.test.c b/src/kernel/skills.test.c index 2e5dee73a..1229c7988 100644 --- a/src/kernel/skills.test.c +++ b/src/kernel/skills.test.c @@ -90,6 +90,32 @@ static void test_skill_change(CuTest *tc) CuAssertIntEquals(tc, 18 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); } +static void test_reduce_skill(CuTest *tc) +{ + unit *u; + + test_setup(); + config_set_int("study.random_progress", 0); + u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + set_number(u, 2); // number should have no effect on skill values + change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); + CuAssertPtrNotNull(tc, unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + CuAssertPtrNotNull(tc, unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, 1 - SKILL_DAYS_PER_WEEK); + CuAssertPtrNotNull(tc, unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 0, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK - 1, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -1); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -1); + CuAssertPtrEquals(tc, NULL, unit_skill(u, SK_CROSSBOW)); +} + static void test_set_level(CuTest * tc) { unit *u; @@ -220,6 +246,7 @@ static void test_skills_merge(CuTest* tc) CuSuite *get_skills_suite(void) { CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_reduce_skill); SUITE_ADD_TEST(suite, test_skill_set); SUITE_ADD_TEST(suite, test_skill_change); SUITE_ADD_TEST(suite, test_set_level); diff --git a/src/study.c b/src/study.c index 552731e35..c9ff33751 100644 --- a/src/study.c +++ b/src/study.c @@ -679,7 +679,7 @@ void produceexp(struct unit *u, enum skill_t sk) if (u->number > 0) { const struct race *rc = u_race(u); if ((rc->flags & RCF_NOLEARN) == 0 && rc_can_learn(rc, sk)) { - change_skill_days(u, sk, produceexp_days() * u->number); + change_skill_days(u, sk, produceexp_days()); } } } diff --git a/src/study.test.c b/src/study.test.c index 089354dd4..9725c5adc 100644 --- a/src/study.test.c +++ b/src/study.test.c @@ -530,6 +530,21 @@ static void test_demon_skillchange_hungry(CuTest *tc) { test_teardown(); } +static void test_produceexp(CuTest *tc) { + unit *u; + skill *sv; + + test_setup(); + + u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + test_set_skill(u, SK_CROSSBOW, 1, 1); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + produceexp(u, SK_CROSSBOW); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2 / 3, sv->days); + test_teardown(); +} + static void test_study_cmd(CuTest *tc) { unit *u; skill *sv; @@ -880,6 +895,7 @@ static void test_teach_many_to_many(CuTest *tc) { CuSuite *get_study_suite(void) { CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_produceexp); SUITE_ADD_TEST(suite, test_study_cmd); SUITE_ADD_TEST(suite, test_study_cost); SUITE_ADD_TEST(suite, test_study_cost_magic); From 13269c60b7b14d63802ca38aad95a543f5b02277 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 13 Feb 2026 16:53:16 +0100 Subject: [PATCH 11/14] fix reduce_skill_days and test --- src/kernel/skills.c | 41 ++++++++++---------- src/kernel/skills.test.c | 80 +++++++++++++++++++++------------------- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/src/kernel/skills.c b/src/kernel/skills.c index 07aa20ebc..a7eab27eb 100644 --- a/src/kernel/skills.c +++ b/src/kernel/skills.c @@ -99,6 +99,12 @@ static void skill_set(skill *sv, unsigned int level, unsigned int days) assert(sv->days == days && sv->level == level); } +int study_speed(const struct race *rc, enum skill_t sk) +{ + int mod = (rc && rc->study_speed) ? rc->study_speed[sk] : 0; + return SKILL_DAYS_PER_WEEK - mod; +} + void sk_set_level(const struct unit *u, skill *sv, unsigned int level) { int weeks = rule_random_progress() ? progress_weeks(level + 1) : (level + 1); @@ -125,25 +131,21 @@ static void increase_skill_days(unit *u, skill *sv, unsigned int days) { static void reduce_skill_days(unit *u, skill *sv, unsigned int days) { if (sv) { + // first, strip full levels off the skill: + // max_days = maximum days I can have "to do" at current level unsigned int max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); - while (sv->days + days > max_days) { - // maximum number of days before we must step down a level: - unsigned int days_lost = max_days - sv->days; - // subtract those days of un-learning, step down a level: - days -= days_lost; - if (sv->level > 0) { - sk_set_level(u, sv, sv->level - 1); - sv->days = 0; - max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); - } - else { - remove_skill(u, (skill_t)sv->id); - sv = NULL; - break; - } + days += sv->days; + while (sv->level > 0 && max_days < days) { + // level_days = expected time to complete current level. + unsigned int level_days = SKILL_DAYS_PER_WEEK * (1 + sv->level); + days -= level_days; + --sv->level; + max_days = MAX_DAYS_TO_NEXT_LEVEL(sv->level); } - if (sv) { - sv->days += days; + // store the remaining days + sv->days = days; + if (sv->level == 0 && sv->days >= SKILL_DAYS_PER_WEEK) { + remove_skill(u, (skill_t)sv->id); } } } @@ -266,8 +268,3 @@ int skill_days(unit *u, enum skill_t sk) return sv ? sv->days : 1; } -int study_speed(const struct race *rc, skill_t sk) -{ - int mod = (rc && rc->study_speed) ? rc->study_speed[sk] : 0; - return SKILL_DAYS_PER_WEEK - mod; -} diff --git a/src/kernel/skills.test.c b/src/kernel/skills.test.c index 1229c7988..654706851 100644 --- a/src/kernel/skills.test.c +++ b/src/kernel/skills.test.c @@ -23,6 +23,13 @@ static void test_skill_set(CuTest *tc) CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, value.days); } +static int days_effort(const skill *sv) +{ + int n = sv->level + 1; + int weeks = (n + 1) * n / 2; + return weeks * SKILL_DAYS_PER_WEEK - sv->days; +} + static void test_skill_change(CuTest *tc) { unit *u; @@ -33,61 +40,49 @@ static void test_skill_change(CuTest *tc) u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); set_number(u, 2); // number should have no effect on skill values change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); - sv = unit_skill(u, SK_CROSSBOW); + CuAssertPtrNotNull(tc, sv = unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 1, sv->level); /* no random progress, so it will take 2 weeks of learning to next level: */ - CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 1, sv->level); - CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 2, sv->level); - CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, 4 * SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 3, sv->level); - CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 6 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 3, sv->level); - CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 3, sv->level); CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, sv->days); change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK * 2); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 3, sv->level); - CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 2, sv->level); - CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, sv->days); CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + sv->level = 10; - // to reach level 11: 21 weeks = 630 days - // of which only 4 weeks left - // => we can lose 17 weeks without losing a level - // losing 8 more, though! - // so the level drop sets us to expected time to get to level 10 (10 weeks) - // plus those 8 should make 18 weeks - // 18 weeks is less or equal to what level 10 needs (19) - change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK * 25); + sv->days = SKILL_DAYS_PER_WEEK; + CuAssertIntEquals(tc, 65 * SKILL_DAYS_PER_WEEK, days_effort(sv)); + + change_skill_days(u, SK_CROSSBOW, -25 * SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 40 * SKILL_DAYS_PER_WEEK, days_effort(sv)); + // 40 = (8 * 9) / 2 + 4 CuAssertIntEquals(tc, 9, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 9, sv->level); - CuAssertIntEquals(tc, 18 * SKILL_DAYS_PER_WEEK, sv->days); - CuAssertIntEquals(tc, 18 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 15 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); } static void test_reduce_skill(CuTest *tc) @@ -99,21 +94,32 @@ static void test_reduce_skill(CuTest *tc) u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); set_number(u, 2); // number should have no effect on skill values change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); - CuAssertPtrNotNull(tc, unit_skill(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); - CuAssertPtrNotNull(tc, unit_skill(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, 1 - SKILL_DAYS_PER_WEEK); - CuAssertPtrNotNull(tc, unit_skill(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, 0, skill_level(u, SK_CROSSBOW)); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK - 1, skill_days(u, SK_CROSSBOW)); change_skill_days(u, SK_CROSSBOW, -1); - CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + CuAssertPtrEquals(tc, NULL, u->skills); + + change_skill_days(u, SK_CROSSBOW, 3 * SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, - 2 * SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, days_effort(u->skills)); + CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -1); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK - 1, days_effort(u->skills)); + CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK + 1, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, 2 - SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, 1, days_effort(u->skills)); change_skill_days(u, SK_CROSSBOW, -1); - CuAssertPtrEquals(tc, NULL, unit_skill(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, skill_level(u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + change_skill_days(u, SK_CROSSBOW, -1); + CuAssertPtrEquals(tc, NULL, u->skills); } static void test_set_level(CuTest * tc) From 783f5309dfb7b55b7560b8df98f3321cd4497788 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 13 Feb 2026 19:28:10 +0100 Subject: [PATCH 12/14] unsigned mess --- src/kernel/skills.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kernel/skills.c b/src/kernel/skills.c index a7eab27eb..9b9d1dd92 100644 --- a/src/kernel/skills.c +++ b/src/kernel/skills.c @@ -81,11 +81,11 @@ static bool rule_random_progress(void) return rule != 0; } -static int progress_weeks(unsigned int level) +static unsigned int progress_weeks(unsigned int level) /* how many weeks must i study to get from level-1 to level */ { unsigned int coins = MAX_WEEKS_TO_NEXT_LEVEL(level - 1) - 1; - int heads = 1; + unsigned int heads = 1; while (coins--) { heads += rng_uint() % 2; } @@ -107,10 +107,10 @@ int study_speed(const struct race *rc, enum skill_t sk) void sk_set_level(const struct unit *u, skill *sv, unsigned int level) { - int weeks = rule_random_progress() ? progress_weeks(level + 1) : (level + 1); + unsigned int weeks = rule_random_progress() ? progress_weeks(level + 1) : (level + 1); const struct race *rc = u ? u_race(u) : NULL; int speed = rc ? study_speed(rc, sv->id) : SKILL_DAYS_PER_WEEK; - int days = SKILL_DAYS_PER_WEEK + (weeks - 1) * speed; + unsigned int days = SKILL_DAYS_PER_WEEK + (weeks - 1) * speed; ASSERT_VALID_SKILL(sv, rc); skill_set(sv, level, days); } From 48112bee3a111080f89f92850c04465543c9cc68 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 13 Feb 2026 19:32:59 +0100 Subject: [PATCH 13/14] eliminate compiler warnings --- src/kernel/messages.test.c | 3 +-- src/kernel/race.test.c | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/kernel/messages.test.c b/src/kernel/messages.test.c index 967545e83..b1e304f03 100644 --- a/src/kernel/messages.test.c +++ b/src/kernel/messages.test.c @@ -6,7 +6,6 @@ #include "util/message.h" #include "util/keyword.h" // for K_ENTERTAIN, K_MOVE -#include "util/variant.h" // for variant #include #include @@ -88,7 +87,7 @@ void test_add_message(CuTest *tc) { CuAssertPtrEquals(tc, msg, mlist->begin->msg); CuAssertPtrEquals(tc, NULL, mlist->begin->next); CuAssertIntEquals(tc, 2, msg->refcount); - msg->is_silent = 1; + msg->is_silent = -1; add_message(&mlist, msg); CuAssertIntEquals(tc, 2, msg->refcount); CuAssertPtrEquals(tc, NULL, mlist->begin->next); diff --git a/src/kernel/race.test.c b/src/kernel/race.test.c index 56ecb8b51..f37d5b69a 100644 --- a/src/kernel/race.test.c +++ b/src/kernel/race.test.c @@ -8,7 +8,6 @@ #include #include -#include #include #include From 08c8086bfe73772450855fcd5f95ced9ea87edb3 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 13 Feb 2026 19:50:10 +0100 Subject: [PATCH 14/14] fix intermittent test build causes produceexp, which spoils the skill for the next test --- src/kernel/build.test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kernel/build.test.c b/src/kernel/build.test.c index 05366fa66..4c4765c1f 100644 --- a/src/kernel/build.test.c +++ b/src/kernel/build.test.c @@ -307,6 +307,7 @@ static void test_build_with_potion_and_ring(CuTest *tc) set_level(u, bf.cons.skill, bf.cons.minskill); CuAssertIntEquals(tc, 1, build(u, 1, &bf.cons, 0, 200, 0)); + set_level(u, bf.cons.skill, bf.cons.minskill); i_change(&u->items, ring, 1); change_effect(u, ptype, 4); CuAssertIntEquals(tc, 11, build(u, 1, &bf.cons, 0, 200, 0));