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
+
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/battle.c b/src/battle.c
index 26cad87e7..d37d846cc 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"
@@ -938,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/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/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/exparse.c b/src/exparse.c
index a80067628..2a8b17b3d 100644
--- a/src/exparse.c
+++ b/src/exparse.c
@@ -28,6 +28,7 @@
#include
#include
+#include
#include
#ifdef XML_LARGE_SIZE
@@ -1070,7 +1071,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];
@@ -1090,8 +1091,8 @@ 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;
- if (speed != 0) {
+ rc->bonus[sk] = (signed char)mod;
+ if (speed != INT_MAX) {
set_study_speed(rc, sk, speed);
}
}
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));
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/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.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/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
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/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..9b9d1dd92 100644
--- a/src/kernel/skills.c
+++ b/src/kernel/skills.c
@@ -5,6 +5,7 @@
#include "unit.h"
#include
+#include
#include
#include
@@ -80,62 +81,96 @@ 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_int() % 2;
+ heads += rng_uint() % 2;
}
return heads;
}
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)
+int study_speed(const struct race *rc, enum skill_t sk)
{
- int weeks = rule_random_progress() ? progress_weeks(level + 1) : (level + 1);
- skill_set(sv, level, weeks * SKILL_DAYS_PER_WEEK);
+ int mod = (rc && rc->study_speed) ? rc->study_speed[sk] : 0;
+ return SKILL_DAYS_PER_WEEK - mod;
}
-void increase_skill_weeks(unit * u, enum skill_t sk, const unsigned int weeks)
+void sk_set_level(const struct unit *u, skill *sv, unsigned int level)
{
- 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(sv, sv->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;
+ unsigned int days = SKILL_DAYS_PER_WEEK + (weeks - 1) * speed;
+ ASSERT_VALID_SKILL(sv, rc);
+ skill_set(sv, level, days);
+}
+
+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(sv->days <= MAX_DAYS_TO_NEXT_LEVEL(sv->level));
}
-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) {
+ // 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);
+ 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);
+ }
+ // store the remaining days
+ sv->days = days;
+ if (sv->level == 0 && sv->days >= SKILL_DAYS_PER_WEEK) {
+ remove_skill(u, (skill_t)sv->id);
+ }
+ }
+}
- 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(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(sv->days <= MAX_DAYS_TO_NEXT_LEVEL(sv->level));
}
int skill_compare(const skill * sk, const skill * sc)
@@ -232,3 +267,4 @@ int skill_days(unit *u, enum skill_t sk)
const skill *sv = unit_skill(u, sk);
return sv ? sv->days : 1;
}
+
diff --git a/src/kernel/skills.h b/src/kernel/skills.h
index a8c26581e..29ad2c356 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;
@@ -35,14 +37,16 @@ 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(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);
/** 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..654706851 100644
--- a/src/kernel/skills.test.c
+++ b/src/kernel/skills.test.c
@@ -17,12 +17,19 @@ 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);
}
+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;
@@ -31,54 +38,88 @@ 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);
- sv = unit_skill(u, SK_CROSSBOW);
+ set_number(u, 2); // number should have no effect on skill values
+ change_skill_days(u, SK_CROSSBOW, 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));
- 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, skill_days(u, SK_CROSSBOW));
- CuAssertIntEquals(tc, 2 * SKILL_DAYS_PER_WEEK, sv->days);
- increase_skill_weeks(u, SK_CROSSBOW, 1);
+ 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, skill_days(u, SK_CROSSBOW));
- CuAssertIntEquals(tc, 1 * SKILL_DAYS_PER_WEEK, sv->days);
- increase_skill_weeks(u, SK_CROSSBOW, 1);
+ 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, skill_days(u, SK_CROSSBOW));
- CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv->days);
- increase_skill_weeks(u, SK_CROSSBOW, 4);
+ 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, skill_days(u, SK_CROSSBOW));
- CuAssertIntEquals(tc, 3 * SKILL_DAYS_PER_WEEK, sv->days);
-
- reduce_skill_weeks(u, sv, 1);
+ 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, skill_days(u, SK_CROSSBOW));
- CuAssertIntEquals(tc, 4 * SKILL_DAYS_PER_WEEK, sv->days);
- reduce_skill_weeks(u, sv, 1);
+ 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);
- reduce_skill_weeks(u, sv, 2);
+ 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, skill_days(u, SK_CROSSBOW));
- CuAssertIntEquals(tc, 7 * SKILL_DAYS_PER_WEEK, sv->days);
- reduce_skill_weeks(u, sv, 1);
+ 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, 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, 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);
+ 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, 15 * 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);
+ 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);
+ 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);
+
+ 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);
+ 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)
@@ -211,6 +252,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/kernel/unit.c b/src/kernel/unit.c
index 2288dffb3..33f6ab24d 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)
@@ -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/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) {
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/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/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..c9ff33751 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;
@@ -687,64 +673,18 @@ 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);
- }
- }
-}
-
-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;
- }
- }
+ change_skill_days(u, sk, produceexp_days());
}
}
}
-/**
+/**
* 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.
@@ -774,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
@@ -809,19 +749,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 +795,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..06619e320 100644
--- a/src/study.h
+++ b/src/study.h
@@ -36,13 +36,9 @@ 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);
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..9725c5adc 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));
@@ -530,19 +530,33 @@ 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;
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 +620,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 +634,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 +646,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 +660,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 +680,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 +710,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 +723,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 +741,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 +757,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 +782,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 +805,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,24 +878,24 @@ 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();
}
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);
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)
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();
}
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
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