From 621c64eb6ad8126d530f107c92a73d822d39b5af Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 20 Sep 2026 21:19:51 +0200 Subject: [PATCH 1/2] change_skill gets total days per unit, not pre-divided by u->number. --- src/kernel/skills.c | 4 ++- src/kernel/skills.test.c | 71 +++++++++++++++++++++++++++++----------- src/study.c | 10 +++--- src/study.test.c | 1 + 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/kernel/skills.c b/src/kernel/skills.c index 0f6acda52..f518f83cf 100644 --- a/src/kernel/skills.c +++ b/src/kernel/skills.c @@ -116,6 +116,7 @@ void sk_set_level(const struct unit *u, skill *sv, unsigned int level) } static void increase_skill(unit *u, skill *sv, unsigned int days) { + days /= u->number; if (days > 0) { unsigned int leveldays = sv->days; while (leveldays <= days) { @@ -130,7 +131,8 @@ static void increase_skill(unit *u, skill *sv, unsigned int days) { void reduce_skill(unit *u, skill *sv, unsigned int days) { - if (sv) { + days /= u->number; + if (sv && days > 0) { // 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); diff --git a/src/kernel/skills.test.c b/src/kernel/skills.test.c index 654706851..cad6b8a25 100644 --- a/src/kernel/skills.test.c +++ b/src/kernel/skills.test.c @@ -38,38 +38,38 @@ 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)); - set_number(u, 2); // number should have no effect on skill values - change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); + set_number(u, 2); + change_skill_days(u, SK_CROSSBOW, 2 * SKILL_DAYS_PER_WEEK); 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)); /* no random progress, so it will take 2 weeks of learning to next level: */ CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, 2 * 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 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, 2 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, days_effort(sv)); 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, 4 * SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, 8 * 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 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, -2 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 6 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, -2 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 5 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK * 2); + change_skill_days(u, SK_CROSSBOW, -4 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 3, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); - change_skill_days(u, SK_CROSSBOW, -SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, -2 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, days_effort(sv)); CuAssertIntEquals(tc, 2, skill_level(u, SK_CROSSBOW)); CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); @@ -78,11 +78,41 @@ static void test_skill_change(CuTest *tc) 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); + change_skill_days(u, SK_CROSSBOW, -50 * 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, 15 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); + test_teardown(); +} + +static void test_change_skill(CuTest *tc) +{ + unit *u; + skill *sv; + + test_setup(); + config_set_int("study.random_progress", 0); + u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + sv = test_set_skill(u, SK_CROSSBOW, 1, 1); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 1, sv->level); + change_skill(u, sv, -SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2, sv->days); + CuAssertIntEquals(tc, 1, sv->level); + + set_number(u, 2); + sv = test_set_skill(u, SK_ARMORER, 1, 1); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 1, sv->level); + change_skill(u, sv, -2 * SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK * 2, sv->days); + CuAssertIntEquals(tc, 1, sv->level); + change_skill(u, sv, 2 * SKILL_DAYS_PER_WEEK); + CuAssertIntEquals(tc, SKILL_DAYS_PER_WEEK, sv->days); + CuAssertIntEquals(tc, 1, sv->level); + + test_teardown(); } static void test_reduce_skill(CuTest *tc) @@ -92,33 +122,33 @@ static void test_reduce_skill(CuTest *tc) 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); + set_number(u, 2); + change_skill_days(u, SK_CROSSBOW, 2 * SKILL_DAYS_PER_WEEK); 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); + change_skill_days(u, SK_CROSSBOW, -2 * SKILL_DAYS_PER_WEEK); 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); + change_skill_days(u, SK_CROSSBOW, -2); CuAssertPtrEquals(tc, NULL, u->skills); - change_skill_days(u, SK_CROSSBOW, 3 * SKILL_DAYS_PER_WEEK); + change_skill_days(u, SK_CROSSBOW, 6 * 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); + change_skill_days(u, SK_CROSSBOW, -4 * 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); + change_skill_days(u, SK_CROSSBOW, -2); 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); + change_skill_days(u, SK_CROSSBOW, 4 - 2 * SKILL_DAYS_PER_WEEK); CuAssertIntEquals(tc, 1, days_effort(u->skills)); - change_skill_days(u, SK_CROSSBOW, -1); + change_skill_days(u, SK_CROSSBOW, -2); 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); + change_skill_days(u, SK_CROSSBOW, -2); CuAssertPtrEquals(tc, NULL, u->skills); } @@ -255,6 +285,7 @@ CuSuite *get_skills_suite(void) 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_change_skill); SUITE_ADD_TEST(suite, test_set_level); SUITE_ADD_TEST(suite, test_skills_merge); return suite; diff --git a/src/study.c b/src/study.c index 8e768aa85..dfe78ddf6 100644 --- a/src/study.c +++ b/src/study.c @@ -414,8 +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) { - // TODO: rounding errors here. - change_skill_days(teacher, sk_academy, academy_students / teacher->number); + // TODO: rounding errors, because increase_skill int-divides by teacher->number again. + change_skill_days(teacher, sk_academy, SKILL_DAYS_PER_WEEK / 3 * academy_students / 10); } reset_order(); return 0; @@ -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()); + change_skill_days(u, sk, u->number * produceexp_days()); } } } @@ -749,7 +749,7 @@ 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 / u->number); + change_skill_days(u, sk, days); return cost; } @@ -789,7 +789,7 @@ void demon_skillchange(unit *u) skill* sv = u->skills + s; int roll = rng_int() % 100; if (sv->level > 0 && roll < upchance + downchance) { - int weeks = 1; + int weeks = u->number; if (max_change > 1) { weeks += rng_int() % max_change; } diff --git a/src/study.test.c b/src/study.test.c index ac3843dcc..84e29e5d2 100644 --- a/src/study.test.c +++ b/src/study.test.c @@ -487,6 +487,7 @@ static void test_demon_skillchange(CuTest *tc) { CuAssertPtrEquals(tc, (void *)rc, (void *)get_race(RC_DAEMON)); u = test_create_unit(test_create_faction_ex(rc, NULL), test_create_plain(0, 0)); CuAssertPtrNotNull(tc, u); + scale_number(u, 2); test_set_skill(u, SK_CROSSBOW, 2, 1); test_set_skill(u, SK_MELEE, 2, 1); CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, skill_days(u, SK_CROSSBOW)); From b0d66aa72667a3edb486b482a9c9bafff8a0f00e Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 21 Sep 2026 17:48:34 +0200 Subject: [PATCH 2/2] Talentverlust durch Schock testen. --- src/triggers/shock.c | 2 +- src/triggers/shock.test.c | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/triggers/shock.c b/src/triggers/shock.c index 460886a1c..9fef753a0 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; - change_skill(u, sv, -SKILL_DAYS_PER_WEEK * change); + change_skill(u, sv, -SKILL_DAYS_PER_WEEK * u->number * change); } } /* Dies ist ein Hack, um das skillmod und familiar-Attribut beim Mage diff --git a/src/triggers/shock.test.c b/src/triggers/shock.test.c index 0f5e0f7e0..50fc9467d 100644 --- a/src/triggers/shock.test.c +++ b/src/triggers/shock.test.c @@ -5,10 +5,12 @@ #include #include #include -#include "kernel/skill.h" // for SK_MAGIC -#include "kernel/types.h" // for M_GWYRRD +#include "kernel/skill.h" +#include "kernel/skills.h" +#include "kernel/types.h" -#include +#include "util/message.h" +#include "util/rand.h" #include #include @@ -23,12 +25,17 @@ static void shock_setup(void) { static void test_shock(CuTest *tc) { unit *u; trigger *tt; + skill *sv; test_setup(); shock_setup(); + random_source_inject_constants(0.f, 0); u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); create_mage(u, M_GWYRRD); - set_level(u, SK_MAGIC, 5); + test_set_skill(u, SK_CROSSBOW, 1, MAX_WEEKS_TO_NEXT_LEVEL(1)); + sv = test_set_skill(u, SK_MAGIC, 5, MAX_WEEKS_TO_NEXT_LEVEL(5)); + CuAssertIntEquals(tc, 5, sv->level); + CuAssertIntEquals(tc, 330, sv->days); set_spellpoints(u, 10); u->hp = 10; tt = trigger_shock(u); @@ -36,6 +43,9 @@ static void test_shock(CuTest *tc) { CuAssertIntEquals(tc, 2, u->hp); CuAssertIntEquals(tc, 2, get_spellpoints(u)); CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "shock")); + // TODO: Talenttageverlust testen + CuAssertIntEquals(tc, 4, sv->level); + CuAssertIntEquals(tc, 180, sv->days); t_free(tt); free(tt); test_teardown();