diff --git a/clibs b/clibs index d86c85254..66a891b38 160000 --- a/clibs +++ b/clibs @@ -1 +1 @@ -Subproject commit d86c8525489d7f11b7ba13e101bb59ecf160b871 +Subproject commit 66a891b383f1a51bb0d4e5cf002530f7f70bf7f4 diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 000000000..f331cdebb Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b267c0d5..cf96cfbc2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,6 +86,7 @@ ENDIF() set (ERESSEA_SRC vortex.c + automate.c move.c piracy.c spells.c @@ -187,6 +188,7 @@ set(TESTS_SRC tests.c academy.test.c alchemy.test.c + automate.test.c battle.test.c creport.test.c direction.test.c diff --git a/src/academy.c b/src/academy.c index 5df13efa4..1298e9b57 100644 --- a/src/academy.c +++ b/src/academy.c @@ -33,13 +33,13 @@ void academy_teaching_bonus(struct unit *u, skill_t sk, int students) { } } -bool academy_can_teach(unit *teacher, unit *student, skill_t sk) { +bool academy_can_teach(unit *teacher, unit *scholar, skill_t sk) { const struct building_type *btype = bt_find("academy"); - if (active_building(teacher, btype) && active_building(student, btype)) { - int j = study_cost(student, sk) * 2; + if (active_building(teacher, btype) && active_building(scholar, btype)) { + int j = study_cost(scholar, sk) * 2; if (j < 50) j = 50; /* kann Einheit das zahlen? */ - return get_pooled(student, get_resourcetype(R_SILVER), GET_DEFAULT, j) >= j; + return get_pooled(scholar, get_resourcetype(R_SILVER), GET_DEFAULT, j) >= j; /* sonst nehmen sie nicht am Unterricht teil */ } return false; diff --git a/src/academy.h b/src/academy.h index f6af93748..3c496d8fa 100644 --- a/src/academy.h +++ b/src/academy.h @@ -9,7 +9,7 @@ extern "C" { struct unit; void academy_teaching_bonus(struct unit *u, skill_t sk, int academy); - bool academy_can_teach(struct unit *teacher, struct unit *student, skill_t sk); + bool academy_can_teach(struct unit *teacher, struct unit *scholar, skill_t sk); #ifdef __cplusplus } #endif diff --git a/src/automate.c b/src/automate.c new file mode 100644 index 000000000..a74cf770b --- /dev/null +++ b/src/automate.c @@ -0,0 +1,161 @@ +#include + +#include "kernel/faction.h" +#include "kernel/messages.h" +#include "kernel/order.h" +#include "kernel/region.h" +#include "kernel/unit.h" + +#include "util/log.h" + +#include "automate.h" +#include "keyword.h" +#include "laws.h" +#include "study.h" + +#include +#include + +static int cmp_scholars(const void *lhs, const void *rhs) +{ + const scholar *a = (const scholar *)lhs; + const scholar *b = (const scholar *)rhs; + if (a->sk == b->sk) { + /* sort by level, descending: */ + return b->level - a->level; + } + /* order by skill */ + return (int)a->sk - (int)b->sk; +} + +int autostudy_init(scholar scholars[], int max_scholars, region *r) +{ + unit *u; + int nscholars = 0; + + for (u = r->units; u; u = u->next) { + keyword_t kwd = getkeyword(u->thisorder); + if (kwd == K_AUTOSTUDY) { + if (long_order_allowed(u) && unit_can_study(u)) { + scholar * st = scholars + nscholars; + if (++nscholars == max_scholars) { + log_fatal("you must increase MAXSCHOLARS"); + } + st->u = u; + init_order(u->thisorder, u->faction->locale); + st->sk = getskill(u->faction->locale); + st->level = effskill_study(u, st->sk); + st->learn = 0; + } + else { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, "error_race_nolearn", "race", + u_race(u))); + } + } + } + if (nscholars > 0) { + qsort(scholars, nscholars, sizeof(scholar), cmp_scholars); + } + return nscholars; +} + +static void teaching(scholar *s, int n) { + assert(n <= s->u->number); + s->learn += n; + s->u->flags |= UFL_LONGACTION; +} + +static void learning(scholar *s, int n) { + assert(n <= s->u->number); + s->learn += n; + s->u->flags |= UFL_LONGACTION; +} + +void autostudy_run(scholar scholars[], int nscholars) +{ + int ti = 0; + while (ti != nscholars) { + skill_t sk = scholars[ti].sk; + int t, s, se, ts = 0, tt = 0, si = ti; + for (se = ti; se != nscholars && scholars[se].sk == sk; ++se) { + int mint; + ts += scholars[se].u->number; /* count total scholars */ + mint = (ts + 10) / 11; /* need a minimum of ceil(ts/11) teachers */ + for (; mint > tt && si != nscholars; ++si) { + tt += scholars[si].u->number; + } + } + /* now si splits the teachers and students 1:10 */ + /* first student must be 2 levels below first teacher: */ + for (; si != se && scholars[ti].level - TEACHDIFFERENCE > scholars[si].level; ++si) { + tt += scholars[si].u->number; + } + if (si == se) { + /* there are no students, so standard learning only */ + for (t = ti; t != se; ++t) { + learning(scholars + t, scholars[t].u->number); + } + } + else { + /* invariant: unit ti can still teach i students */ + int i = scholars[ti].u->number * STUDENTS_PER_TEACHER; + /* invariant: unit si has n students that can still be taught */ + int n = scholars[si].u->number; + for (t = ti, s = si; t != si && s != se; ) { + if (i > n) { + /* t has more than enough teaching capacity for s */ + i -= n; + teaching(scholars + s, n); + learning(scholars + s, scholars[s].u->number); + /* next student, please: */ + if (++s == se) { + continue; + } + n = scholars[s].u->number; + } + else { + /* s gets partial credit and we need a new teacher */ + teaching(scholars + s, i); + + /* we are done with this teacher. any remaining people are regular learners: */ + if (scholars[t].u->number > 1) { + /* remain = number - ceil(taught/10); */ + int remain = (STUDENTS_PER_TEACHER * scholars[t].u->number - i + STUDENTS_PER_TEACHER - 1) / STUDENTS_PER_TEACHER; + learning(scholars + t, remain); + } + + /* we want a new teacher for s. if any exists, it's next in the sequence. */ + if (++t == si) { + continue; + } + if (scholars[t].level - TEACHDIFFERENCE < scholars[s].level) { + /* next teacher cannot teach, we must skip students. */ + do { + learning(scholars + s, (n - i)); + i = 0; + if (++s == se) { + continue; + } + n = scholars[s].u->number; + } while (scholars[t].level - TEACHDIFFERENCE < scholars[s].level); + } + i = scholars[t].u->number * STUDENTS_PER_TEACHER; + } + } + } + ti = se; + } +} + +#define MAXSCHOLARS 128 + +void do_autostudy(region *r) +{ + scholar scholars[MAXSCHOLARS]; + int i, nscholars = autostudy_init(scholars, MAXSCHOLARS, r); + autostudy_run(scholars, nscholars); + for (i = 0; i != nscholars; ++i) { + int days = STUDYDAYS * scholars[i].learn; + learn_skill(scholars[i].u, scholars[i].sk, days); + } +} diff --git a/src/automate.h b/src/automate.h new file mode 100644 index 000000000..61fed6866 --- /dev/null +++ b/src/automate.h @@ -0,0 +1,43 @@ +/* +Copyright (c) 1998-2018, Enno Rehling +Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#pragma once + +#ifndef H_GC_AUTOMATE +#define H_GC_AUTOMATE + +#include "skill.h" + +struct region; +struct unit; + +typedef struct scholar { + struct unit *u; + skill_t sk; + int level; + int learn; +} scholar; + +#define STUDENTS_PER_TEACHER 10 + +void do_autostudy(struct region *r); + +int autostudy_init(scholar scholars[], int max_scholars, struct region *r); +void autostudy_run(scholar scholars[], int nscholars); + +#endif diff --git a/src/automate.test.c b/src/automate.test.c new file mode 100644 index 000000000..a77376668 --- /dev/null +++ b/src/automate.test.c @@ -0,0 +1,114 @@ +#ifdef _MSC_VER +#include +#endif + +#include "automate.h" + +#include "kernel/faction.h" +#include "kernel/order.h" +#include "kernel/region.h" +#include "kernel/unit.h" + +#include "tests.h" + +#include + +static void test_autostudy_init(CuTest *tc) { + scholar scholars[4]; + unit *u1, *u2, *u3; + faction *f; + region *r; + + test_setup(); + r = test_create_plain(0, 0); + f = test_create_faction(NULL); + u1 = test_create_unit(f, r); + u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]); + test_create_unit(f, r); + u2 = test_create_unit(f, r); + u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]); + set_level(u2, SK_ENTERTAINMENT, 2); + u3 = test_create_unit(f, r); + u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]); + scholars[3].u = NULL; + CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, r)); + CuAssertPtrEquals(tc, u2, scholars[0].u); + CuAssertIntEquals(tc, 2, scholars[0].level); + CuAssertIntEquals(tc, 0, scholars[0].learn); + CuAssertIntEquals(tc, SK_ENTERTAINMENT, scholars[0].sk); + CuAssertPtrEquals(tc, u1, scholars[1].u); + CuAssertIntEquals(tc, 0, scholars[1].level); + CuAssertIntEquals(tc, 0, scholars[1].learn); + CuAssertIntEquals(tc, SK_ENTERTAINMENT, scholars[1].sk); + CuAssertPtrEquals(tc, u3, scholars[2].u); + CuAssertIntEquals(tc, 0, scholars[2].level); + CuAssertIntEquals(tc, 0, scholars[2].learn); + CuAssertIntEquals(tc, SK_PERCEPTION, scholars[2].sk); + CuAssertPtrEquals(tc, NULL, scholars[3].u); + test_teardown(); +} + +static void test_autostudy_run(CuTest *tc) { + scholar scholars[4]; + unit *u1, *u2, *u3; + faction *f; + region *r; + + test_setup(); + r = test_create_plain(0, 0); + f = test_create_faction(NULL); + u1 = test_create_unit(f, r); + u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]); + set_number(u1, 2); + set_level(u1, SK_ENTERTAINMENT, 2); + u2 = test_create_unit(f, r); + u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]); + set_number(u2, 10); + u3 = test_create_unit(f, r); + u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]); + set_number(u3, 15); + scholars[3].u = NULL; + CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, r)); + autostudy_run(scholars, 3); + CuAssertIntEquals(tc, 0, scholars[0].learn); + CuAssertIntEquals(tc, 20, scholars[1].learn); + CuAssertIntEquals(tc, 15, scholars[2].learn); + test_teardown(); +} + +static void test_autostudy_run_noteachers(CuTest *tc) { + scholar scholars[4]; + unit *u1, *u2, *u3; + faction *f; + region *r; + + test_setup(); + r = test_create_plain(0, 0); + f = test_create_faction(NULL); + u1 = test_create_unit(f, r); + u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_LUMBERJACK]); + set_number(u1, 2); + set_level(u1, SK_ENTERTAINMENT, 2); + u2 = test_create_unit(f, r); + u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]); + set_number(u2, 10); + u3 = test_create_unit(f, r); + u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]); + set_number(u3, 15); + scholars[3].u = NULL; + CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, r)); + autostudy_run(scholars, 3); + CuAssertIntEquals(tc, 2, scholars[0].learn); + CuAssertIntEquals(tc, 10, scholars[1].learn); + CuAssertIntEquals(tc, 15, scholars[2].learn); + test_teardown(); +} + +CuSuite *get_automate_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_autostudy_init); + SUITE_ADD_TEST(suite, test_autostudy_run); + SUITE_ADD_TEST(suite, test_autostudy_run_noteachers); + return suite; +} diff --git a/src/kernel/config.c b/src/kernel/config.c index ec45ddd0d..750d406f6 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -135,7 +135,8 @@ const char *parameters[MAXPARAMS] = { "GRUPPE", "PARTEITARNUNG", "BAEUME", - "ALLIANZ" + "ALLIANZ", + "AUTO" }; int findoption(const char *s, const struct locale *lang) diff --git a/src/kernel/order.c b/src/kernel/order.c index e660edf81..342f45577 100644 --- a/src/kernel/order.c +++ b/src/kernel/order.c @@ -84,7 +84,7 @@ char* get_command(const order *ord, const struct locale *lang, char *sbuffer, si sbs_strcat(&sbs, str); if (ord->id < 0) { skill_t sk = (skill_t)(100+ord->id); - assert(kwd == K_STUDY && sk != SK_MAGIC && sk < MAXSKILLS); + assert((kwd == K_STUDY || kwd == K_AUTOSTUDY) && sk != SK_MAGIC && sk < MAXSKILLS); str = skillname(sk, lang); if (str) { if (strchr(str, ' ') == NULL) { @@ -214,12 +214,12 @@ static int create_data(keyword_t kwd, const char *s, order_data *data; int id; - assert(kwd!=NOKEYWORD); + assert(kwd != NOKEYWORD); if (!s || *s == 0) { return 0; } - if (kwd==K_STUDY) { + if (kwd == K_STUDY || kwd == K_AUTOSTUDY) { const char * sptr = s; skill_t sk = get_skill(parse_token_depr(&sptr), lang); if (sk != SK_MAGIC && sk != NOSKILL) { @@ -332,6 +332,14 @@ order *parse_order(const char *s, const struct locale * lang) sptr = sp; } } + else if (kwd == K_STUDY) { + const char *sp = sptr; + p = parse_token_depr(&sp); + if (p && isparam(p, lang, P_AUTO)) { + kwd = K_AUTOSTUDY; + sptr = sp; + } + } if (kwd != NOKEYWORD) { order *ord = (order *)malloc(sizeof(order)); create_order_i(ord, kwd, sptr, persistent, noerror, lang); @@ -366,6 +374,7 @@ bool is_repeated(keyword_t kwd) case K_STEAL: case K_SABOTAGE: case K_STUDY: + case K_AUTOSTUDY: case K_TEACH: case K_GROW: case K_PLANT: @@ -406,6 +415,7 @@ bool is_exclusive(const order * ord) case K_STEAL: case K_SABOTAGE: case K_STUDY: + case K_AUTOSTUDY: case K_TEACH: case K_GROW: case K_PLANT: @@ -447,6 +457,7 @@ bool is_long(keyword_t kwd) case K_STEAL: case K_SABOTAGE: case K_STUDY: + case K_AUTOSTUDY: case K_TEACH: case K_GROW: case K_PLANT: @@ -541,7 +552,7 @@ keyword_t init_order(const struct order *ord, const struct locale *lang) assert(sk < MAXSKILLS); assert(lang); - assert(kwd == K_STUDY); + assert(kwd == K_STUDY || kwd == K_AUTOSTUDY); str = skillname(sk, lang); if (strchr(str, ' ') == NULL) { init_tokens_str(str); @@ -575,7 +586,7 @@ keyword_t init_order_depr(const struct order *ord) { if (ord) { keyword_t kwd = ORD_KEYWORD(ord); - assert(kwd != K_STUDY); + assert(kwd != K_STUDY && kwd != K_AUTOSTUDY); } return init_order(ord, NULL); } diff --git a/src/kernel/order.test.c b/src/kernel/order.test.c index 3a2f44c56..d1f8b4176 100644 --- a/src/kernel/order.test.c +++ b/src/kernel/order.test.c @@ -113,6 +113,30 @@ static void test_parse_make(CuTest *tc) { test_teardown(); } +static void test_parse_autostudy(CuTest *tc) { + char cmd[32]; + order *ord; + struct locale * lang; + + test_setup(); + lang = get_or_create_locale("en"); + locale_setstring(lang, mkname("skill", skillnames[SK_ENTERTAINMENT]), "Entertainment"); + locale_setstring(lang, keyword(K_STUDY), "STUDY"); + locale_setstring(lang, keyword(K_AUTOSTUDY), "AUTOSTUDY"); + locale_setstring(lang, parameters[P_AUTO], "AUTO"); + init_locale(lang); + + ord = parse_order("STUDY AUTO Entertainment", lang); + CuAssertPtrNotNull(tc, ord); + CuAssertIntEquals(tc, K_AUTOSTUDY, getkeyword(ord)); + CuAssertStrEquals(tc, "AUTOSTUDY Entertainment", get_command(ord, lang, cmd, sizeof(cmd))); + + CuAssertIntEquals(tc, K_AUTOSTUDY, init_order(ord, lang)); + CuAssertStrEquals(tc, "Entertainment", getstrtoken()); + free_order(ord); + test_teardown(); +} + static void test_parse_make_temp(CuTest *tc) { char cmd[32]; order *ord; @@ -130,7 +154,7 @@ static void test_parse_make_temp(CuTest *tc) { CuAssertIntEquals(tc, K_MAKETEMP, getkeyword(ord)); CuAssertStrEquals(tc, "MAKETEMP herp", get_command(ord, lang, cmd, sizeof(cmd))); - CuAssertIntEquals(tc, K_MAKETEMP, init_order_depr(ord)); + CuAssertIntEquals(tc, K_MAKETEMP, init_order(ord, lang)); CuAssertStrEquals(tc, "herp", getstrtoken()); free_order(ord); test_teardown(); @@ -153,7 +177,7 @@ static void test_parse_maketemp(CuTest *tc) { CuAssertPtrNotNull(tc, ord); CuAssertStrEquals(tc, "MAKETEMP herp", get_command(ord, lang, cmd, sizeof(cmd))); CuAssertIntEquals(tc, K_MAKETEMP, getkeyword(ord)); - CuAssertIntEquals(tc, K_MAKETEMP, init_order_depr(ord)); + CuAssertIntEquals(tc, K_MAKETEMP, init_order(ord, lang)); CuAssertStrEquals(tc, "herp", getstrtoken()); free_order(ord); test_teardown(); @@ -167,7 +191,7 @@ static void test_init_order(CuTest *tc) { lang = get_or_create_locale("en"); ord = create_order(K_MAKETEMP, lang, "hurr durr"); - CuAssertIntEquals(tc, K_MAKETEMP, init_order_depr(ord)); + CuAssertIntEquals(tc, K_MAKETEMP, init_order(ord, lang)); CuAssertStrEquals(tc, "hurr", getstrtoken()); CuAssertStrEquals(tc, "durr", getstrtoken()); free_order(ord); @@ -548,6 +572,7 @@ CuSuite *get_order_suite(void) SUITE_ADD_TEST(suite, test_study_order_quoted); SUITE_ADD_TEST(suite, test_parse_order); SUITE_ADD_TEST(suite, test_parse_make); + SUITE_ADD_TEST(suite, test_parse_autostudy); SUITE_ADD_TEST(suite, test_parse_make_temp); SUITE_ADD_TEST(suite, test_parse_maketemp); SUITE_ADD_TEST(suite, test_init_order); diff --git a/src/kernel/types.h b/src/kernel/types.h index 61f1268cc..48a4326fa 100644 --- a/src/kernel/types.h +++ b/src/kernel/types.h @@ -130,6 +130,7 @@ typedef enum { P_FACTIONSTEALTH, P_TREES, P_ALLIANCE, + P_AUTO, MAXPARAMS, NOPARAM } param_t; diff --git a/src/kernel/unit.c b/src/kernel/unit.c index 07c6c0856..770723bea 100644 --- a/src/kernel/unit.c +++ b/src/kernel/unit.c @@ -1310,13 +1310,12 @@ int eff_skill(const unit * u, const skill *sv, const region *r) return 0; } -int effskill_study(const unit * u, skill_t sk, const region * r) +int effskill_study(const unit * u, skill_t sk) { skill *sv = unit_skill(u, sk); if (sv && sv->level > 0) { int mlevel = sv->level; - if (!r) r = u->region; - mlevel += get_modifier(u, sv->id, sv->level, r, true); + mlevel += get_modifier(u, sv->id, sv->level, u->region, true); if (mlevel > 0) return mlevel; } diff --git a/src/kernel/unit.h b/src/kernel/unit.h index ac61affde..a6135715c 100644 --- a/src/kernel/unit.h +++ b/src/kernel/unit.h @@ -163,7 +163,7 @@ extern "C" { void clone_men(const struct unit *src, struct unit *dst, int n); /* like transfer, but do not subtract from src */ int eff_skill(const struct unit *u, const struct skill *sv, const struct region *r); - int effskill_study(const struct unit *u, skill_t sk, const struct region *r); + int effskill_study(const struct unit *u, skill_t sk); int get_modifier(const struct unit *u, skill_t sk, int level, const struct region *r, bool noitem); diff --git a/src/keyword.c b/src/keyword.c index 7519b6449..48726bcbc 100644 --- a/src/keyword.c +++ b/src/keyword.c @@ -149,5 +149,6 @@ const char *keywords[MAXKEYWORDS] = { "promote", "pay", "loot", + "autostudy", }; diff --git a/src/keyword.h b/src/keyword.h index a9273c3f5..6e4832708 100644 --- a/src/keyword.h +++ b/src/keyword.h @@ -72,6 +72,7 @@ extern "C" K_PROMOTION, K_PAY, K_LOOT, + K_AUTOSTUDY, MAXKEYWORDS, NOKEYWORD } keyword_t; diff --git a/src/laws.c b/src/laws.c index 8d75b0897..68f2894b3 100644 --- a/src/laws.c +++ b/src/laws.c @@ -26,6 +26,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include "alchemy.h" +#include "automate.h" #include "battle.h" #include "economy.h" #include "keyword.h" @@ -3636,6 +3637,24 @@ void add_proc_unit(int priority, void(*process) (unit *), const char *name) } } +bool long_order_allowed(const unit *u) +{ + const region *r = u->region; + if (fval(u, UFL_LONGACTION)) { + /* this message was already given in laws.update_long_order + cmistake(u, ord, 52, MSG_PRODUCE); + */ + return false; + } + else if (fval(r->terrain, SEA_REGION) + && u_race(u) != get_race(RC_AQUARIAN) + && !(u_race(u)->flags & RCF_SWIM)) { + /* error message disabled by popular demand */ + return false; + } + return true; +} + /* per priority, execute processors in order from PR_GLOBAL down to PR_ORDER */ void process(void) { @@ -3705,16 +3724,7 @@ void process(void) cmistake(u, ord, 224, MSG_MAGIC); ord = NULL; } - else if (fval(u, UFL_LONGACTION)) { - /* this message was already given in laws.update_long_order - cmistake(u, ord, 52, MSG_PRODUCE); - */ - ord = NULL; - } - else if (fval(r->terrain, SEA_REGION) - && u_race(u) != get_race(RC_AQUARIAN) - && !(u_race(u)->flags & RCF_SWIM)) { - /* error message disabled by popular demand */ + else if (!long_order_allowed(u)) { ord = NULL; } } @@ -4001,6 +4011,7 @@ void init_processor(void) } p += 10; + add_proc_region(p, do_autostudy, "study automation"); add_proc_order(p, K_TEACH, teach_cmd, PROC_THISORDER | PROC_LONGORDER, "Lehren"); p += 10; @@ -4155,7 +4166,7 @@ void update_subscriptions(void) /** determine if unit can be seen by faction * @param f -- the observiong faction * @param u -- the unit that is observed - * @param r -- the region that u is obesrved in (see below) + * @param r -- the region that u is obesrved from (see below) * @param m -- terrain modifier to stealth * * r kann != u->region sein, wenn es um Durchreisen geht, diff --git a/src/laws.h b/src/laws.h index ae2c712ca..0b5db17c7 100755 --- a/src/laws.h +++ b/src/laws.h @@ -66,6 +66,7 @@ extern "C" { void update_long_order(struct unit *u); void sinkships(struct region * r); void do_enter(struct region *r, bool is_final_attempt); + bool long_order_allowed(const struct unit *u); int password_cmd(struct unit *u, struct order *ord); int banner_cmd(struct unit *u, struct order *ord); diff --git a/src/laws.test.c b/src/laws.test.c index 8d13b7c81..c7c7a6682 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -1759,6 +1759,34 @@ static void test_nmr_timeout(CuTest *tc) { test_teardown(); } +static void test_long_orders(CuTest *tc) { + unit *u; + + test_setup(); + u = test_create_unit(test_create_faction(NULL), test_create_plain(0, 0)); + CuAssertTrue(tc, long_order_allowed(u)); + u->flags |= UFL_LONGACTION; + CuAssertTrue(tc, !long_order_allowed(u)); + test_teardown(); +} + +static void test_long_order_on_ocean(CuTest *tc) { + unit *u; + race * rc; + + test_setup(); + rc = test_create_race("pikachu"); + u = test_create_unit(test_create_faction(rc), test_create_ocean(0, 0)); + CuAssertTrue(tc, !long_order_allowed(u)); + rc->flags |= RCF_SWIM; + CuAssertTrue(tc, long_order_allowed(u)); + + rc = test_create_race("aquarian"); + u = test_create_unit(test_create_faction(rc), u->region); + CuAssertTrue(tc, long_order_allowed(u)); + test_teardown(); +} + CuSuite *get_laws_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -1831,6 +1859,8 @@ CuSuite *get_laws_suite(void) SUITE_ADD_TEST(suite, test_cansee_ring); SUITE_ADD_TEST(suite, test_cansee_sphere); SUITE_ADD_TEST(suite, test_nmr_timeout); + SUITE_ADD_TEST(suite, test_long_orders); + SUITE_ADD_TEST(suite, test_long_order_on_ocean); return suite; } diff --git a/src/study.c b/src/study.c index 46da90b74..bd29fc2a8 100644 --- a/src/study.c +++ b/src/study.c @@ -69,7 +69,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #define TEACH_ALL 1 #define TEACH_FRIENDS -static skill_t getskill(const struct locale *lang) +skill_t getskill(const struct locale *lang) { char token[128]; const char * s = gettoken(token, sizeof(token)); @@ -195,37 +195,37 @@ const attrib_type at_learning = { #define EXPERIENCEDAYS 10 -static int study_days(unit * student, skill_t sk) +static int study_days(unit * scholar, skill_t sk) { int speed = STUDYDAYS; - if (u_race(student)->study_speed) { - speed += u_race(student)->study_speed[sk]; + if (u_race(scholar)->study_speed) { + speed += u_race(scholar)->study_speed[sk]; if (speed < STUDYDAYS) { - skill *sv = unit_skill(student, sk); + skill *sv = unit_skill(scholar, sk); if (sv == 0) { speed = STUDYDAYS; } } } - return student->number * speed; + return scholar->number * speed; } static int -teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk, +teach_unit(unit * teacher, unit * scholar, int nteaching, skill_t sk, bool report, int *academy_students) { teaching_info *teach = NULL; attrib *a; int students; - if (magic_lowskill(student)) { + if (magic_lowskill(scholar)) { cmistake(teacher, teacher->thisorder, 292, MSG_EVENT); return 0; } - students = student->number; + students = scholar->number; /* subtract already taught students */ - a = a_find(student->attribs, &at_learning); + a = a_find(scholar->attribs, &at_learning); if (a != NULL) { teach = (teaching_info *)a->data.v; students -= teach->students; @@ -235,18 +235,18 @@ teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk, if (students > 0) { if (teach == NULL) { - a = a_add(&student->attribs, a_new(&at_learning)); + a = a_add(&scholar->attribs, a_new(&at_learning)); teach = (teaching_info *)a->data.v; } selist_push(&teach->teachers, teacher); teach->days += students * STUDYDAYS; teach->students += students; - if (student->building && teacher->building == student->building) { + if (scholar->building && teacher->building == scholar->building) { /* Solange Akademien groessenbeschraenkt sind, sollte Lehrer und * Student auch in unterschiedlichen Gebaeuden stehen duerfen */ /* FIXME comment contradicts implementation */ - if (academy_can_teach(teacher, student, sk)) { + if (academy_can_teach(teacher, scholar, sk)) { /* Jeder Schueler zusaetzlich +10 Tage wenn in Uni. */ teach->days += students * EXPERIENCEDAYS; /* learning erhoehen */ /* Lehrer zusaetzlich +1 Tag pro Schueler. */ @@ -304,7 +304,7 @@ int teach_cmd(unit * teacher, struct order *ord) #if TEACH_ALL if (getparam(teacher->faction->locale) == P_ANY) { skill_t sk; - unit *student; + unit *scholar; skill_t teachskill[MAXSKILLS]; int t = 0; @@ -313,15 +313,15 @@ int teach_cmd(unit * teacher, struct order *ord) teachskill[t] = getskill(teacher->faction->locale); } while (sk != NOSKILL); - for (student = r->units; teaching > 0 && student; student = student->next) { - if (LongHunger(student)) { + for (scholar = r->units; teaching > 0 && scholar; scholar = scholar->next) { + if (LongHunger(scholar)) { continue; } - else if (student->faction == teacher->faction) { - if (getkeyword(student->thisorder) == K_STUDY) { + else if (scholar->faction == teacher->faction) { + if (getkeyword(scholar->thisorder) == K_STUDY) { /* Input ist nun von student->thisorder !! */ - init_order(student->thisorder, student->faction->locale); - sk = getskill(student->faction->locale); + init_order(scholar->thisorder, scholar->faction->locale); + sk = getskill(scholar->faction->locale); if (sk != NOSKILL && teachskill[0] != NOSKILL) { for (t = 0; teachskill[t] != NOSKILL; ++t) { if (sk == teachskill[t]) { @@ -331,20 +331,20 @@ int teach_cmd(unit * teacher, struct order *ord) sk = teachskill[t]; } if (sk != NOSKILL - && effskill_study(teacher, sk, 0) - TEACHDIFFERENCE > effskill_study(student, sk, 0)) { - teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students); + && effskill_study(teacher, sk) - TEACHDIFFERENCE > effskill_study(scholar, sk)) { + teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students); } } } #ifdef TEACH_FRIENDS - else if (alliedunit(teacher, student->faction, HELP_GUARD)) { - if (getkeyword(student->thisorder) == K_STUDY) { + else if (alliedunit(teacher, scholar->faction, HELP_GUARD)) { + if (getkeyword(scholar->thisorder) == K_STUDY) { /* Input ist nun von student->thisorder !! */ - init_order(student->thisorder, student->faction->locale); - sk = getskill(student->faction->locale); + init_order(scholar->thisorder, scholar->faction->locale); + sk = getskill(scholar->faction->locale); if (sk != NOSKILL - && effskill_study(teacher, sk, 0) - TEACHDIFFERENCE >= effskill(student, sk, 0)) { - teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students); + && effskill_study(teacher, sk) - TEACHDIFFERENCE >= effskill(scholar, sk, NULL)) { + teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students); } } } @@ -363,15 +363,15 @@ int teach_cmd(unit * teacher, struct order *ord) while (!parser_end()) { skill_t sk; - unit *student; + unit *scholar; bool feedback; - getunit(r, teacher->faction, &student); + getunit(r, teacher->faction, &scholar); ++count; /* Falls die Unit nicht gefunden wird, Fehler melden */ - if (!student) { + if (!scholar) { char tbuf[20]; const char *uid; const char *token; @@ -403,8 +403,8 @@ int teach_cmd(unit * teacher, struct order *ord) continue; } - feedback = teacher->faction == student->faction - || alliedunit(student, teacher->faction, HELP_GUARD); + feedback = teacher->faction == scholar->faction + || alliedunit(scholar, teacher->faction, HELP_GUARD); /* Neuen Befehl zusammenbauen. TEMP-Einheiten werden automatisch in * ihre neuen Nummern uebersetzt. */ @@ -412,31 +412,31 @@ int teach_cmd(unit * teacher, struct order *ord) strncat(zOrder, " ", sz - 1); --sz; } - sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(student->no), sz); + sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(scholar->no), sz); - if (getkeyword(student->thisorder) != K_STUDY) { + if (getkeyword(scholar->thisorder) != K_STUDY) { ADDMSG(&teacher->faction->msgs, - msg_feedback(teacher, ord, "teach_nolearn", "student", student)); + msg_feedback(teacher, ord, "teach_nolearn", "student", scholar)); continue; } /* Input ist nun von student->thisorder !! */ parser_pushstate(); - init_order(student->thisorder, student->faction->locale); - sk = getskill(student->faction->locale); + init_order(scholar->thisorder, scholar->faction->locale); + sk = getskill(scholar->faction->locale); parser_popstate(); if (sk == NOSKILL) { ADDMSG(&teacher->faction->msgs, - msg_feedback(teacher, ord, "teach_nolearn", "student", student)); + msg_feedback(teacher, ord, "teach_nolearn", "student", scholar)); continue; } - if (effskill_study(student, sk, 0) > effskill_study(teacher, sk, 0) + if (effskill_study(scholar, sk) > effskill_study(teacher, sk) - TEACHDIFFERENCE) { if (feedback) { ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord, "teach_asgood", - "student", student)); + "student", scholar)); } continue; } @@ -444,18 +444,18 @@ int teach_cmd(unit * teacher, struct order *ord) /* ist der Magier schon spezialisiert, so versteht er nur noch * Lehrer seines Gebietes */ sc_mage *mage1 = get_mage_depr(teacher); - sc_mage *mage2 = get_mage_depr(student); + sc_mage *mage2 = get_mage_depr(scholar); if (mage2 && mage1 && mage2->magietyp != M_GRAY && mage1->magietyp != mage2->magietyp) { if (feedback) { ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord, - "error_different_magic", "target", student)); + "error_different_magic", "target", scholar)); } continue; } } sk_academy = sk; - teaching -= teach_unit(teacher, student, teaching, sk, false, &academy_students); + teaching -= teach_unit(teacher, scholar, teaching, sk, false, &academy_students); } new_order = create_order(K_TEACH, teacher->faction->locale, "%s", zOrder); replace_order(&teacher->orders, ord, new_order); @@ -766,9 +766,6 @@ int study_cmd(unit * u, order * ord) days *= 2; } - if (fval(u, UFL_HUNGER)) - days /= 2; - learn_skill(u, sk, days); if (a != NULL) { if (teach->teachers) { @@ -832,6 +829,9 @@ void learn_skill(unit *u, skill_t sk, int days) { int leveldays = STUDYDAYS * u->number; int weeks = 0; + if (fval(u, UFL_HUNGER)) { + days /= 2; + } assert(sk >= 0 && sk < MAXSKILLS); if (inject_learn_fun) { inject_learn_fun(u, sk, days); diff --git a/src/study.h b/src/study.h index b4b76bb8c..e99fb4808 100644 --- a/src/study.h +++ b/src/study.h @@ -33,6 +33,7 @@ extern "C" { int study_cmd(struct unit *u, struct order *ord); magic_t getmagicskill(const struct locale *lang); + skill_t getskill(const struct locale *lang); bool is_migrant(struct unit *u); int study_cost(struct unit *u, skill_t talent); diff --git a/src/study.test.c b/src/study.test.c index c1d0b0841..a63be7cf1 100644 --- a/src/study.test.c +++ b/src/study.test.c @@ -89,15 +89,15 @@ static void setup_teacher(study_fixture *fix, skill_t sk) { setup_locale(lang); fix->u = test_create_unit(f, r); assert(fix->u); - fix->u->thisorder = create_order(K_STUDY, f->locale, "%s", skillnames[sk]); + fix->u->thisorder = create_order(K_STUDY, f->locale, skillnames[sk]); fix->teachers[0] = test_create_unit(f, r); assert(fix->teachers[0]); - fix->teachers[0]->thisorder = create_order(K_TEACH, f->locale, "%s", itoa36(fix->u->no)); + fix->teachers[0]->thisorder = create_order(K_TEACH, f->locale, itoa36(fix->u->no)); fix->teachers[1] = test_create_unit(f, r); assert(fix->teachers[1]); - fix->teachers[1]->thisorder = create_order(K_TEACH, f->locale, "%s", itoa36(fix->u->no)); + fix->teachers[1]->thisorder = create_order(K_TEACH, f->locale, itoa36(fix->u->no)); test_clear_messages(f); } @@ -110,7 +110,7 @@ static void test_study_no_teacher(CuTest *tc) { CuAssertPtrNotNull(tc, sv = unit_skill(fix.u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1, sv->level); CuAssertIntEquals(tc, 2, sv->weeks); - CuAssertPtrEquals(tc, 0, test_get_last_message(fix.u->faction->msgs)); + CuAssertPtrEquals(tc, NULL, test_get_last_message(fix.u->faction->msgs)); test_teardown(); } @@ -121,7 +121,7 @@ static void test_study_with_teacher(CuTest *tc) { setup_teacher(&fix, SK_CROSSBOW); set_level(fix.teachers[0], SK_CROSSBOW, TEACHDIFFERENCE); teach_cmd(fix.teachers[0], fix.teachers[0]->thisorder); - CuAssertPtrEquals(tc, 0, test_get_last_message(fix.u->faction->msgs)); + CuAssertPtrEquals(tc, NULL, test_get_last_message(fix.u->faction->msgs)); study_cmd(fix.u, fix.u->thisorder); CuAssertPtrNotNull(tc, sv = unit_skill(fix.u, SK_CROSSBOW)); CuAssertIntEquals(tc, 1, sv->level); @@ -288,7 +288,7 @@ static void test_academy_bonus(CuTest *tc) { u1 = test_create_unit(u->faction, u->region); u3 = test_create_unit(u->faction, u->region); u0->thisorder = create_order(K_TEACH, loc, "%s %s", itoa36(u3->no), itoa36(u1->no)); - u->thisorder = create_order(K_TEACH, loc, "%s", itoa36(u1->no)); + u->thisorder = create_order(K_TEACH, loc, itoa36(u1->no)); u1->thisorder = create_order(K_STUDY, loc, skillnames[SK_CROSSBOW]); u3->thisorder = create_order(K_STUDY, loc, skillnames[SK_CROSSBOW]); @@ -405,7 +405,7 @@ static void test_study_magic(CuTest *tc) { f = test_create_faction(NULL); lang = f->locale; u = test_create_unit(f, test_create_region(0, 0, NULL)); - u->thisorder = create_order(K_STUDY, lang, "%s", skillnames[SK_MAGIC]); + u->thisorder = create_order(K_STUDY, lang, skillnames[SK_MAGIC]); itype = test_create_silver(); CuAssertIntEquals(tc, -1, study_cmd(u, u->thisorder)); @@ -423,7 +423,7 @@ static void test_study_magic(CuTest *tc) { CuAssertIntEquals(tc, M_GWYRRD, f->magiegebiet); CuAssertIntEquals(tc, 0, i_get(u->items, itype)); CuAssertPtrNotNull(tc, get_mage_depr(u)); - CuAssertPtrEquals(tc, 0, test_find_messagetype(f->msgs, "error65")); + CuAssertPtrEquals(tc, NULL, test_find_messagetype(f->msgs, "error65")); CuAssertIntEquals(tc, M_GWYRRD, get_mage_depr(u)->magietyp); test_teardown(); @@ -491,12 +491,12 @@ static void test_teach_magic(CuTest *tc) { f = test_create_faction(NULL); f->magiegebiet = M_GWYRRD; u = test_create_unit(f, test_create_region(0, 0, NULL)); - u->thisorder = create_order(K_STUDY, f->locale, "%s", skillnames[SK_MAGIC]); + u->thisorder = create_order(K_STUDY, f->locale, skillnames[SK_MAGIC]); i_change(&u->items, itype, study_cost(u, SK_MAGIC)); ut = test_create_unit(f, u->region); set_level(ut, SK_MAGIC, TEACHDIFFERENCE); create_mage(ut, M_GWYRRD); - ut->thisorder = create_order(K_TEACH, f->locale, "%s", itoa36(u->no)); + ut->thisorder = create_order(K_TEACH, f->locale, itoa36(u->no)); learn_inject(); teach_cmd(ut, ut->thisorder); study_cmd(u, u->thisorder); diff --git a/src/test_eressea.c b/src/test_eressea.c index c299fc09c..5684a8ad4 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -93,7 +93,6 @@ int RunAllTests(int argc, char *argv[]) ADD_SUITE(xerewards); /* kernel */ ADD_SUITE(academy); - ADD_SUITE(alchemy); ADD_SUITE(alliance); ADD_SUITE(ally); ADD_SUITE(building); @@ -121,6 +120,8 @@ int RunAllTests(int argc, char *argv[]) ADD_SUITE(spells); ADD_SUITE(unit); /* gamecode */ + ADD_SUITE(alchemy); + ADD_SUITE(automate); ADD_SUITE(battle); ADD_SUITE(calendar); ADD_SUITE(creport); diff --git a/src/util/.DS_Store b/src/util/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/src/util/.DS_Store differ