diff --git a/.travis.yml b/.travis.yml index de62c2200..dd3f469ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ addons: - libncurses5-dev - libsqlite3-dev - libxml2-dev + - valgrind os: - linux - osx diff --git a/res/core/messages.xml b/res/core/messages.xml index 4e78fc094..237ab8c7a 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -2830,17 +2830,6 @@ "$unit($unit) in $region($region) produziert $int($amount)$if($eq($wanted,$amount),""," von $int($wanted)") $resource($resource,$wanted)." "$unit($unit) in $region($region) produces $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") $resource($resource,$amount)." - - - - - - - - - "$unit($unit) in $region($region) produziert $int($amount)$if($eq($wanted,$amount),""," von $int($wanted)") $resource($resource,$wanted)." - "$unit($unit) in $region($region) produces $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") $resource($resource,$amount)." - diff --git a/s/travis-build b/s/travis-build index ad2c8607b..14aff5358 100755 --- a/s/travis-build +++ b/s/travis-build @@ -16,3 +16,5 @@ s/build cd $ROOT inifile s/runtests +cd tests +./write-reports.sh diff --git a/scripts/eressea/jsreport.lua b/scripts/eressea/jsreport.lua index 442b27d5f..0351efd12 100644 --- a/scripts/eressea/jsreport.lua +++ b/scripts/eressea/jsreport.lua @@ -1,7 +1,5 @@ local pkg = {} -print("loading jsreport module") - function pkg.init() eressea.settings.set("feature.jsreport.enable", "1") end diff --git a/scripts/tests/e3/rules.lua b/scripts/tests/e3/rules.lua index 0e9d52147..902a91741 100644 --- a/scripts/tests/e3/rules.lua +++ b/scripts/tests/e3/rules.lua @@ -460,7 +460,6 @@ function test_canoe_passes_through_land() u1:add_order("NACH O O O") process_orders() assert_equal(land, u2.region, "canoe did not stop at coast") - u1:add_order("NACH O O O") process_orders() assert_equal(dst, sh.region, "canoe could not leave coast") assert_equal(dst, u1.region, "canoe could not leave coast") diff --git a/scripts/tests/e3/spells.lua b/scripts/tests/e3/spells.lua index c4f0aadaf..f99e4687b 100644 --- a/scripts/tests/e3/spells.lua +++ b/scripts/tests/e3/spells.lua @@ -58,7 +58,6 @@ function test_magic() u:add_spell("protective_runes") u:add_spell("analyze_magic") u:clear_orders() - u:add_order("ZAUBERE \"Runen des Schutzes\" BURG " .. itoa36(b.id)); u.building = b u:add_order("ZAUBERE \"Magie analysieren\" BURG " .. itoa36(b.id)); process_orders() diff --git a/scripts/tests/study.lua b/scripts/tests/study.lua index e5ab84018..9185535d7 100644 --- a/scripts/tests/study.lua +++ b/scripts/tests/study.lua @@ -6,13 +6,19 @@ function setup() conf = [[{ "races" : { "human" : {} }, "terrains" : { "plain" : { "flags" : [ "land" ] } }, - "keywords" : { "de" : { "study": "LERNEN" } }, - "skills" : { "de": { "alchemy" : "Alchemie", "crossbow" : "Armbrust" } }, + "keywords" : { "de" : { "study": "LERNEN", "teach": "LEHREN" } }, + "skills" : { "de": { + "tactics" : "Taktik", + "alchemy" : "Alchemie", + "crossbow" : "Armbrust" + } }, "spells" : { "fireball" : { "syntax" : "u+" } } }]] eressea.game.reset() eressea.config.reset(); eressea.settings.set('rules.magic.playerschools', '') + eressea.settings.set("rules.economy.food", "4") + eressea.settings.set('study.random_progress', '0') eressea.config.parse(conf) end @@ -51,3 +57,62 @@ function test_unit_spells() assert_equal("fireball", sp.name) assert_equal(2, sp.level) end + +local function make_teacher(student, f, skill) + f = f or student.faction + local u = unit.create(f, student.region, 1) + u:clear_orders() + u:add_order("LEHRE " .. itoa36(student.id)) + u:set_skill(skill or "crossbow", 10) + return u +end + +local function make_student(f, r, num, skill) + local u = unit.create(f, r, num or 1) + u:clear_orders() + u:add_order("LERNE " .. (skill or "Armbrust")) + return u +end + +function test_study_no_teacher() + local r = region.create(0, 0, "plain") + local f = faction.create("test@example.com", "human", "de") + local u1 = make_student(f, r, 1) + u1:set_skill("crossbow", 1) + process_orders() + assert_equal(1, u1:get_skill("crossbow")) +end + +function test_study_with_teacher() + local r = region.create(0, 0, "plain") + local f = faction.create("test@example.com", "human", "de") + local u1 = make_student(f, r, 1) + + make_teacher(u1) + u1:set_skill("crossbow", 1) + process_orders() + assert_equal(2, u1:get_skill("crossbow")) +end + +function test_study_too_many_students() + local r = region.create(0, 0, "plain") + local f = faction.create("test@example.com", "human", "de") + local u1 = make_student(f, r, 20, "Taktik") + u1.name = "Student" + u1:add_item("money", 201*u1.number) + make_teacher(u1, f, "tactics") + process_orders() + assert_equal(u1.number, u1:get_item("money")) +end + +function test_study_multiple_teachers() + local r = region.create(0, 0, "plain") + local f = faction.create("test@example.com", "human", "de") + local u1 = make_student(f, r, 20, "Taktik") + u1.name = "Student" + u1:add_item("money", 201*u1.number) + make_teacher(u1, f, "tactics") + make_teacher(u1, f, "tactics") + process_orders() + assert_equal(u1.number, u1:get_item("money")) +end diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27c85c8de..c2cd21396 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -193,6 +193,7 @@ set(TESTS_SRC skill.test.c spells.test.c spy.test.c + study.test.c upkeep.test.c spells/magicresistance.test.c ${ATTRIBUTES_TESTS} diff --git a/src/battle.c b/src/battle.c index 8f2a07b91..daf96ce1e 100644 --- a/src/battle.c +++ b/src/battle.c @@ -3231,7 +3231,7 @@ fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) } /* Illusionen und Zauber kaempfen nicht */ - if (fval(u_race(u), RCF_ILLUSIONARY) || idle(u->faction) || u->number == 0) { + if (fval(u_race(u), RCF_ILLUSIONARY) || u->number == 0) { return NULL; } if (s1 == NULL) { diff --git a/src/bind_process.c b/src/bind_process.c index c411e257a..2e883d38a 100755 --- a/src/bind_process.c +++ b/src/bind_process.c @@ -159,7 +159,7 @@ void process_quit(void) { void process_study(void) { process_cmd(K_TEACH, teach_cmd, PROC_LONG_ORDER); - process_cmd(K_STUDY, learn_cmd, PROC_LONG_ORDER); + process_cmd(K_STUDY, study_cmd, PROC_LONG_ORDER); } void process_movement(void) { diff --git a/src/creport.c b/src/creport.c index 38e63e299..1ce5c2a30 100644 --- a/src/creport.c +++ b/src/creport.c @@ -68,6 +68,7 @@ without prior permission by the authors of Eressea. #include #include #include +#include /* libc includes */ #include @@ -176,7 +177,7 @@ static void print_items(FILE * F, item * items, const struct locale *lang) } static void -cr_output_curses(FILE * F, const faction * viewer, const void *obj, objtype_t typ) +cr_output_curses(stream *out, const faction * viewer, const void *obj, objtype_t typ) { bool header = false; attrib *a = NULL; @@ -258,10 +259,10 @@ cr_output_curses(FILE * F, const faction * viewer, const void *obj, objtype_t ty char buf[BUFFERSIZE]; if (!header) { header = 1; - fputs("EFFECTS\n", F); + stream_printf(out, "EFFECTS\n"); } nr_render(msg, viewer->locale, buf, sizeof(buf), viewer); - fprintf(F, "\"%s\"\n", buf); + stream_printf(out, "\"%s\"\n", buf); msg_release(msg); } a = a->next; @@ -272,9 +273,9 @@ cr_output_curses(FILE * F, const faction * viewer, const void *obj, objtype_t ty const char *key = resourcename(data->type->itype->rtype, 0); if (!header) { header = 1; - fputs("EFFECTS\n", F); + stream_printf(out, "EFFECTS\n"); } - fprintf(F, "\"%d %s\"\n", data->value, translate(key, + stream_printf(out, "\"%d %s\"\n", data->value, translate(key, LOC(default_locale, key))); } a = a->next; @@ -285,6 +286,13 @@ cr_output_curses(FILE * F, const faction * viewer, const void *obj, objtype_t ty } } +static void cr_output_curses_compat(FILE *F, const faction * viewer, const void *obj, objtype_t typ) { + // TODO: eliminate this function + stream strm; + fstream_init(&strm, F); + cr_output_curses(&strm, viewer, obj, typ); +} + static int cr_unit(variant var, char *buffer, const void *userdata) { unit *u = (unit *)var.v; @@ -636,7 +644,7 @@ faction * f) fprintf(F, "%d;Partei\n", fno); if (b->besieged) fprintf(F, "%d;Belagerer\n", b->besieged); - cr_output_curses(F, f, b, TYP_BUILDING); + cr_output_curses_compat(F, f, b, TYP_BUILDING); } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ @@ -682,30 +690,22 @@ const faction * f, const region * r) if (w != NODIRECTION) fprintf(F, "%d;Kueste\n", w); - cr_output_curses(F, f, sh, TYP_SHIP); + cr_output_curses_compat(F, f, sh, TYP_SHIP); } -static void -fwriteorder(FILE * F, const struct order *ord, const struct locale *lang, -bool escape) -{ +static int stream_order(stream *out, const struct order *ord) { + const char *str; char ebuf[1025]; char obuf[1024]; - const char *str = obuf; - fputc('"', F); write_order(ord, obuf, sizeof(obuf)); - if (escape) { - str = escape_string(obuf, ebuf, sizeof(ebuf)); - if (str == ebuf) { - ebuf[1024] = 0; - } + str = escape_string(obuf, ebuf, sizeof(ebuf)); + if (str == ebuf) { + ebuf[1024] = 0; } - if (str[0]) - fputs(str, F); - fputc('"', F); + return stream_printf(out, "\"%s\"\n", str); } -static void cr_output_spells(FILE * F, const unit * u, int maxlevel) +static void cr_output_spells(stream *out, const unit * u, int maxlevel) { spellbook * book = unit_get_spellbook(u); @@ -720,17 +720,20 @@ static void cr_output_spells(FILE * F, const unit * u, int maxlevel) spell * sp = sbe->sp; const char *name = translate(mkname("spell", sp->sname), spell_name(sp, f->locale)); if (!header) { - fputs("SPRUECHE\n", F); + stream_printf(out, "SPRUECHE\n"); header = 1; } - fprintf(F, "\"%s\"\n", name); + stream_printf(out, "\"%s\"\n", name); } } } } -/* prints all that belongs to a unit */ -static void cr_output_unit(FILE * F, const region * r, const faction * f, /* observers faction */ +/** prints all that belongs to a unit +* @param f observers faction +* @param u unit to report +*/ +void cr_output_unit(stream *out, const region * r, const faction * f, const unit * u, int mode) { /* Race attributes are always plural and item attributes always @@ -751,7 +754,7 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, const char *prefix; assert(u && u->number); - if (u != NULL || fval(u_race(u), RCF_INVISIBLE)) + if (fval(u_race(u), RCF_INVISIBLE)) return; if (!init) { @@ -763,11 +766,11 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, itemcloak = cu && curse_active(cu); } - fprintf(F, "EINHEIT %d\n", u->no); - fprintf(F, "\"%s\";Name\n", unit_getname(u)); + stream_printf(out, "EINHEIT %d\n", u->no); + stream_printf(out, "\"%s\";Name\n", unit_getname(u)); str = u_description(u, f->locale); if (str) { - fprintf(F, "\"%s\";Beschr\n", str); + stream_printf(out, "\"%s\";Beschr\n", str); } /* print faction information */ sf = visible_faction(f, u); @@ -784,41 +787,41 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, a = a_find(u->attribs, &at_group); if (a != NULL) { const group *g = (const group *)a->data.v; - fprintf(F, "%d;gruppe\n", g->gid); + stream_printf(out, "%d;gruppe\n", g->gid); } - fprintf(F, "%d;Partei\n", u->faction->no); + stream_printf(out, "%d;Partei\n", u->faction->no); if (sf != u->faction) - fprintf(F, "%d;Verkleidung\n", sf->no); + stream_printf(out, "%d;Verkleidung\n", sf->no); if (fval(u, UFL_ANON_FACTION)) - fprintf(F, "%d;Parteitarnung\n", i2b(fval(u, UFL_ANON_FACTION))); + stream_printf(out, "%d;Parteitarnung\n", i2b(fval(u, UFL_ANON_FACTION))); if (otherfaction) { if (otherfaction != u->faction) { - fprintf(F, "%d;Anderepartei\n", otherfaction->no); + stream_printf(out, "%d;Anderepartei\n", otherfaction->no); } } mage = get_familiar_mage(u); if (mage) { - fprintf(F, "%u;familiarmage\n", mage->no); + stream_printf(out, "%u;familiarmage\n", mage->no); } } else { if (fval(u, UFL_ANON_FACTION)) { /* faction info is hidden */ - fprintf(F, "%d;Parteitarnung\n", i2b(fval(u, UFL_ANON_FACTION))); + stream_printf(out, "%d;Parteitarnung\n", i2b(fval(u, UFL_ANON_FACTION))); } else { const attrib *a_otherfaction = a_find(u->attribs, &at_otherfaction); const faction *otherfaction = a_otherfaction ? get_otherfaction(a_otherfaction) : NULL; /* other unit. show visible faction, not u->faction */ - fprintf(F, "%d;Partei\n", sf->no); + stream_printf(out, "%d;Partei\n", sf->no); if (sf == f) { - fprintf(F, "1;Verraeter\n"); + stream_printf(out, "1;Verraeter\n"); } if (a_otherfaction) { if (otherfaction != u->faction) { if (alliedunit(u, f, HELP_FSTEALTH)) { - fprintf(F, "%d;Anderepartei\n", otherfaction->no); + stream_printf(out, "%d;Anderepartei\n", otherfaction->no); } } } @@ -826,53 +829,53 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, } if (prefix) { prefix = mkname("prefix", prefix); - fprintf(F, "\"%s\";typprefix\n", translate(prefix, LOC(f->locale, + stream_printf(out, "\"%s\";typprefix\n", translate(prefix, LOC(f->locale, prefix))); } if (u->faction != f && a_fshidden && a_fshidden->data.ca[0] == 1 && effskill(u, SK_STEALTH) >= 6) { - fprintf(F, "-1;Anzahl\n"); + stream_printf(out, "-1;Anzahl\n"); } else { - fprintf(F, "%d;Anzahl\n", u->number); + stream_printf(out, "%d;Anzahl\n", u->number); } pzTmp = get_racename(u->attribs); if (pzTmp) { - fprintf(F, "\"%s\";Typ\n", pzTmp); + stream_printf(out, "\"%s\";Typ\n", pzTmp); if (u->faction == f && fval(u_race(u), RCF_SHAPESHIFTANY)) { const char *zRace = rc_name_s(u_race(u), NAME_PLURAL); - fprintf(F, "\"%s\";wahrerTyp\n", + stream_printf(out, "\"%s\";wahrerTyp\n", translate(zRace, LOC(f->locale, zRace))); } } else { const race *irace = u_irace(u); const char *zRace = rc_name_s(irace, NAME_PLURAL); - fprintf(F, "\"%s\";Typ\n", + stream_printf(out, "\"%s\";Typ\n", translate(zRace, LOC(f->locale, zRace))); if (u->faction == f && irace != u_race(u)) { assert(skill_enabled(SK_STEALTH) || !"we're resetting this on load, so.. ircase should never be used"); zRace = rc_name_s(u_race(u), NAME_PLURAL); - fprintf(F, "\"%s\";wahrerTyp\n", + stream_printf(out, "\"%s\";wahrerTyp\n", translate(zRace, LOC(f->locale, zRace))); } } if (u->building) { assert(u->building->region); - fprintf(F, "%d;Burg\n", u->building->no); + stream_printf(out, "%d;Burg\n", u->building->no); } if (u->ship) { assert(u->ship->region); - fprintf(F, "%d;Schiff\n", u->ship->no); + stream_printf(out, "%d;Schiff\n", u->ship->no); } if (is_guard(u, GUARD_ALL) != 0) { - fprintf(F, "%d;bewacht\n", 1); + stream_printf(out, "%d;bewacht\n", 1); } if ((b = usiege(u)) != NULL) { - fprintf(F, "%d;belagert\n", b->no); + stream_printf(out, "%d;belagert\n", b->no); } /* additional information for own units */ if (u->faction == f || omniscient(f)) { @@ -884,56 +887,55 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, i = ualias(u); if (i > 0) - fprintf(F, "%d;temp\n", i); + stream_printf(out, "%d;temp\n", i); else if (i < 0) - fprintf(F, "%d;alias\n", -i); + stream_printf(out, "%d;alias\n", -i); i = get_money(u); - fprintf(F, "%d;Kampfstatus\n", u->status); - fprintf(F, "%d;weight\n", weight(u)); + stream_printf(out, "%d;Kampfstatus\n", u->status); + stream_printf(out, "%d;weight\n", weight(u)); if (fval(u, UFL_NOAID)) { - fputs("1;unaided\n", F); + stream_printf(out, "1;unaided\n"); } if (fval(u, UFL_STEALTH)) { i = u_geteffstealth(u); if (i >= 0) { - fprintf(F, "%d;Tarnung\n", i); + stream_printf(out, "%d;Tarnung\n", i); } } xc = uprivate(u); if (xc) { - fprintf(F, "\"%s\";privat\n", xc); + stream_printf(out, "\"%s\";privat\n", xc); } c = hp_status(u); if (c && *c && (u->faction == f || omniscient(f))) { - fprintf(F, "\"%s\";hp\n", translate(c, + stream_printf(out, "\"%s\";hp\n", translate(c, LOC(u->faction->locale, c))); } if (fval(u, UFL_HERO)) { - fputs("1;hero\n", F); + stream_printf(out, "1;hero\n"); } if (fval(u, UFL_HUNGER) && (u->faction == f)) { - fputs("1;hunger\n", F); + stream_printf(out, "1;hunger\n"); } if (is_mage(u)) { - fprintf(F, "%d;Aura\n", get_spellpoints(u)); - fprintf(F, "%d;Auramax\n", max_spellpoints(u->region, u)); + stream_printf(out, "%d;Aura\n", get_spellpoints(u)); + stream_printf(out, "%d;Auramax\n", max_spellpoints(u->region, u)); } /* default commands */ - fprintf(F, "COMMANDS\n"); + stream_printf(out, "COMMANDS\n"); for (ord = u->old_orders; ord; ord = ord->next) { /* this new order will replace the old defaults */ if (is_persistent(ord)) { - fwriteorder(F, ord, f->locale, true); - fputc('\n', F); + stream_order(out, ord); } } for (ord = u->orders; ord; ord = ord->next) { - if (u->old_orders && is_repeated(ord)) + keyword_t kwd = getkeyword(ord); + if (u->old_orders && is_repeated(kwd)) continue; /* unit has defaults */ if (is_persistent(ord)) { - fwriteorder(F, ord, f->locale, true); - fputc('\n', F); + stream_order(out, ord); } } @@ -945,9 +947,9 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, int esk = eff_skill(u, sk, r); if (!pr) { pr = 1; - fprintf(F, "TALENTE\n"); + stream_printf(out, "TALENTE\n"); } - fprintf(F, "%d %d;%s\n", u->number * level_days(sv->level), esk, + stream_printf(out, "%d %d;%s\n", u->number * level_days(sv->level), esk, translate(mkname("skill", skillnames[sk]), skillname(sk, f->locale))); } @@ -957,7 +959,7 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, mage = get_mage(u); if (mage) { int i, maxlevel = effskill(u, SK_MAGIC); - cr_output_spells(F, u, maxlevel); + cr_output_spells(out, u, maxlevel); for (i = 0; i != MAXCOMBATSPELLS; ++i) { const spell *sp = mage->combatspells[i].sp; @@ -965,9 +967,9 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, const char *name = translate(mkname("spell", sp->sname), spell_name(sp, f->locale)); - fprintf(F, "KAMPFZAUBER %d\n", i); - fprintf(F, "\"%s\";name\n", name); - fprintf(F, "%d;level\n", mage->combatspells[i].level); + stream_printf(out, "KAMPFZAUBER %d\n", i); + stream_printf(out, "\"%s\";name\n", name); + stream_printf(out, "%d;level\n", mage->combatspells[i].level); } } } @@ -1000,12 +1002,21 @@ static void cr_output_unit(FILE * F, const region * r, const faction * f, continue; if (!pr) { pr = 1; - fputs("GEGENSTAENDE\n", F); + stream_printf(out, "GEGENSTAENDE\n"); } - fprintf(F, "%d;%s\n", in, translate(ic, LOC(f->locale, ic))); + stream_printf(out, "%d;%s\n", in, translate(ic, LOC(f->locale, ic))); } - cr_output_curses(F, f, u, TYP_UNIT); + cr_output_curses(out, f, u, TYP_UNIT); +} + +static void cr_output_unit_compat(FILE * F, const region * r, const faction * f, + const unit * u, int mode) +{ + // TODO: eliminate this function + stream strm; + fstream_init(&strm, F); + cr_output_unit(&strm, r, f, u, mode); } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ @@ -1376,7 +1387,7 @@ static void cr_output_region(FILE * F, report_context * ctx, seen_region * sr) if (r->land) { print_items(F, r->land->items, f->locale); } - cr_output_curses(F, f, r, TYP_REGION); + cr_output_curses_compat(F, f, r, TYP_REGION); cr_borders(ctx->seen, r, f, sr->mode, F); if (sr->mode == see_unit && is_astral(r) && !is_cursed(r->attribs, C_ASTRALBLOCK, 0)) { @@ -1468,7 +1479,7 @@ static void cr_output_region(FILE * F, report_context * ctx, seen_region * sr) if (u->building || u->ship || (stealthmod > INT_MIN && cansee(f, r, u, stealthmod))) { - cr_output_unit(F, r, f, u, sr->mode); + cr_output_unit_compat(F, r, f, u, sr->mode); } } } diff --git a/src/creport.h b/src/creport.h index 678f09ada..c2c2ff385 100644 --- a/src/creport.h +++ b/src/creport.h @@ -15,10 +15,16 @@ extern "C" { #endif + struct stream; + struct region; + struct faction; + struct unit; + void creport_cleanup(void); void register_cr(void); int crwritemap(const char *filename); + void cr_output_unit(struct stream *out, const struct region * r, const struct faction * f, const struct unit * u, int mode); #ifdef __cplusplus } diff --git a/src/economy.c b/src/economy.c index 23ccb3fde..8cf46845b 100644 --- a/src/economy.c +++ b/src/economy.c @@ -563,7 +563,7 @@ static void recruit(unit * u, struct order *ord, request ** recruitorders) return; } } - if (!playerrace(rc) || idle(u->faction)) { + if (!playerrace(rc)) { cmistake(u, ord, 139, MSG_EVENT); return; } @@ -979,26 +979,13 @@ void economics(region * r) remove_empty_units_in_region(r); for (u = r->units; u; u = u->next) { - order *ord; - bool destroyed = false; - if (u->number > 0) { - for (ord = u->orders; ord; ord = ord->next) { - keyword_t kwd = getkeyword(ord); - if (kwd == K_DESTROY) { - if (!destroyed) { - if (destroy_cmd(u, ord) != 0) - ord = NULL; - destroyed = true; - } - } - if (u->orders == NULL) { - break; - } + order *ord = u->thisorder; + keyword_t kwd = getkeyword(ord); + if (kwd == K_DESTROY) { + if (destroy_cmd(u, ord) == 0) { + fset(u, UFL_LONGACTION | UFL_NOTMOVING); } } - if (destroyed) { - fset(u, UFL_LONGACTION | UFL_NOTMOVING); - } } } @@ -1051,7 +1038,7 @@ static void manufacture(unit * u, const item_type * itype, int want) i_change(&u->items, itype, n); if (want == INT_MAX) want = n; - ADDMSG(&u->faction->msgs, msg_message("manufacture", + ADDMSG(&u->faction->msgs, msg_message("produce", "unit region amount wanted resource", u, u->region, n, want, itype->rtype)); } @@ -1466,7 +1453,7 @@ static void create_potion(unit * u, const potion_type * ptype, int want) i_change(&u->items, ptype->itype, built); if (want == INT_MAX) want = built; - ADDMSG(&u->faction->msgs, msg_message("manufacture", + ADDMSG(&u->faction->msgs, msg_message("produce", "unit region amount wanted resource", u, u->region, built, want, ptype->itype->rtype)); break; @@ -3216,7 +3203,7 @@ void produce(struct region *r) continue; if (fval(u, UFL_LONGACTION) && u->thisorder == NULL) { - /* this message was already given in laws.setdefaults + /* this message was already given in laws.c:update_long_order cmistake(u, u->thisorder, 52, MSG_PRODUCE); */ continue; diff --git a/src/jsreport.c b/src/jsreport.c index 6d9f710c2..44f015053 100644 --- a/src/jsreport.c +++ b/src/jsreport.c @@ -59,9 +59,11 @@ static int report_json(const char *filename, report_context * ctx, const char *c if (sr) { terrain_t ter = oldterrain(r->terrain); if (ter == NOTERRAIN) { - log_warning("report_json: %s has no terrain id\n", r->terrain->_name); + data = 1 + r->terrain->_name[0]; + } + else { + data = 1 + (int)ter; } - data = 1 + (int)ter; } } fprintf(F, "%d", data); diff --git a/src/kernel/build.c b/src/kernel/build.c index fcb116741..37006e968 100644 --- a/src/kernel/build.c +++ b/src/kernel/build.c @@ -151,11 +151,11 @@ int destroy_cmd(unit * u, struct order *ord) int n = INT_MAX; if (u->number < 1) - return 0; + return 1; if (fval(u, UFL_LONGACTION)) { cmistake(u, ord, 52, MSG_PRODUCE); - return 0; + return 52; } init_order(ord); @@ -183,11 +183,11 @@ int destroy_cmd(unit * u, struct order *ord) if (u != building_owner(b)) { cmistake(u, ord, 138, MSG_PRODUCE); - return 0; + return 138; } if (fval(b->type, BTF_INDESTRUCTIBLE)) { cmistake(u, ord, 138, MSG_PRODUCE); - return 0; + return 138; } if (n >= b->size) { /* destroy completly */ @@ -213,11 +213,11 @@ int destroy_cmd(unit * u, struct order *ord) if (u != ship_owner(sh)) { cmistake(u, ord, 138, MSG_PRODUCE); - return 0; + return 138; } if (fval(r->terrain, SEA_REGION)) { cmistake(u, ord, 14, MSG_EVENT); - return 0; + return 14; } if (n >= (sh->size * 100) / sh->type->construction->maxsize) { @@ -242,11 +242,11 @@ int destroy_cmd(unit * u, struct order *ord) } else { cmistake(u, ord, 138, MSG_PRODUCE); - return 0; + return 138; } if (con) { - /* TODO: Nicht an ZERSTÖRE mit Punktangabe angepaßt! */ + /* TODO: Nicht an ZERSTÖRE mit Punktangabe angepasst! */ int c; for (c = 0; con->materials[c].number; ++c) { const requirement *rq = con->materials + c; diff --git a/src/kernel/config.c b/src/kernel/config.c index ee8a2638c..9c26ad85a 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -530,7 +530,9 @@ int alliedunit(const unit * u, const faction * f2, int mode) ally *sf; int automode; - assert(u && u->region); /* the unit should be in a region, but it's possible that u->number==0 (TEMP units) */ + assert(u); + assert(f2); + assert(u->region); /* the unit should be in a region, but it's possible that u->number==0 (TEMP units) */ if (u->faction == f2) return mode; if (u->faction != NULL && f2 != NULL) { @@ -904,11 +906,6 @@ int newcontainerid(void) return random_no; } -bool idle(faction * f) -{ - return (bool)(f ? false : true); -} - int maxworkingpeasants(const struct region *r) { int size = production(r); @@ -922,6 +919,10 @@ static const char * parameter_key(int i) return parameters[i]; } +void init_parameters(struct locale *lang) { + init_translations(lang, UT_PARAMS, parameter_key, MAXPARAMS); +} + void init_terrains_translation(const struct locale *lang) { void **tokens; @@ -1012,7 +1013,7 @@ void init_locale(struct locale *lang) if (name) addtoken(tokens, name, var); } - init_translations(lang, UT_PARAMS, parameter_key, MAXPARAMS); + init_parameters(lang); init_options_translation(lang); init_terrains_translation(lang); @@ -1684,6 +1685,11 @@ void kernel_init(void) } static order * defaults[MAXLOCALES]; +keyword_t default_keyword = NOKEYWORD; + +void set_default_order(int kwd) { + default_keyword = (keyword_t)kwd; +} order *default_order(const struct locale *lang) { @@ -1691,6 +1697,11 @@ order *default_order(const struct locale *lang) int i = locale_index(lang); order *result = 0; assert(i < MAXLOCALES); + + if (default_keyword!=NOKEYWORD) { + return create_order(default_keyword, lang, 0); + } + result = defaults[i]; if (!result && usedefault) { const char * str = LOC(lang, "defaultorder"); diff --git a/src/kernel/config.h b/src/kernel/config.h index 4ca76871c..056de8f84 100644 --- a/src/kernel/config.h +++ b/src/kernel/config.h @@ -179,7 +179,6 @@ extern "C" { bool has_limited_skills(const struct unit *u); const struct race *findrace(const char *, const struct locale *); - bool idle(struct faction *f); bool unit_has_cursed_item(const struct unit *u); /* grammatik-flags: */ @@ -287,8 +286,12 @@ extern "C" { int AllianceAuto(void); /* flags that allied factions get automatically */ int AllianceRestricted(void); /* flags restricted to allied factions */ int HelpMask(void); /* flags restricted to allied factions */ + struct order *default_order(const struct locale *lang); + void set_default_order(int kwd); + int entertainmoney(const struct region *r); + void init_parameters(struct locale *lang); void free_gamedata(void); diff --git a/src/kernel/messages.c b/src/kernel/messages.c index e0efdd04b..28625ff85 100644 --- a/src/kernel/messages.c +++ b/src/kernel/messages.c @@ -285,8 +285,9 @@ extern unsigned int new_hashstring(const char *s); void free_messagelist(message_list * msgs) { - struct mlist **mlistptr = &msgs->begin; - while (*mlistptr) { + struct mlist **mlistptr; + assert(msgs && msgs->begin); + for (mlistptr = &msgs->begin; *mlistptr;) { struct mlist *ml = *mlistptr; *mlistptr = ml->next; msg_release(ml->msg); diff --git a/src/kernel/order.c b/src/kernel/order.c index 20cce5853..90bea11d3 100644 --- a/src/kernel/order.c +++ b/src/kernel/order.c @@ -115,7 +115,9 @@ char* get_command(const order *ord, char *sbuffer, size_t size) { assert(str); if (text) --size; bytes = (int)strlcpy(bufp, str, size); - if (wrptr(&bufp, &size, bytes) != 0) WARN_STATIC_BUFFER(); + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } if (text) *bufp++ = ' '; } else { @@ -252,6 +254,19 @@ static order_data *create_data(keyword_t kwd, const char *sptr, int lindex) return data; } +static void free_localedata(int lindex) { + int i; + for (i = 0; i != MAXKEYWORDS; ++i) { + release_data(locale_array[lindex]->short_orders[i]); + locale_array[lindex]->short_orders[i] = 0; + } + for (i = 0; i != MAXSKILLS; ++i) { + release_data(locale_array[lindex]->study_orders[i]); + locale_array[lindex]->study_orders[i] = 0; + } + locale_array[lindex]->lang = 0; +} + static order *create_order_i(keyword_t kwd, const char *sptr, bool persistent, const struct locale *lang) { @@ -276,7 +291,12 @@ static order *create_order_i(keyword_t kwd, const char *sptr, bool persistent, lindex = locale_index(lang); assert(lindex < MAXLOCALES); - locale_array[lindex] = (locale_data *)calloc(1, sizeof(locale_data)); + if (!locale_array[lindex]) { + locale_array[lindex] = (locale_data *)calloc(1, sizeof(locale_data)); + } + else if (locale_array[lindex]->lang != lang) { + free_localedata(lindex); + } locale_array[lindex]->lang = lang; ord = (order *)malloc(sizeof(order)); @@ -292,13 +312,13 @@ order *create_order(keyword_t kwd, const struct locale * lang, const char *params, ...) { char zBuffer[DISPLAYSIZE]; - assert(lang); if (params) { char *bufp = zBuffer; int bytes; size_t size = sizeof(zBuffer) - 1; va_list marker; + assert(lang); va_start(marker, params); while (*params) { if (*params == '%') { @@ -389,9 +409,8 @@ order *parse_order(const char *s, const struct locale * lang) * \return true if the order is long * \sa is_exclusive(), is_repeated(), is_persistent() */ -bool is_repeated(const order * ord) +bool is_repeated(keyword_t kwd) { - keyword_t kwd = ORD_KEYWORD(ord); switch (kwd) { case K_CAST: case K_BUY: @@ -468,10 +487,8 @@ bool is_exclusive(const order * ord) * \return true if the order is long * \sa is_exclusive(), is_repeated(), is_persistent() */ -bool is_long(const order * ord) +bool is_long(keyword_t kwd) { - keyword_t kwd = ORD_KEYWORD(ord); - switch (kwd) { case K_CAST: case K_BUY: @@ -522,7 +539,7 @@ bool is_persistent(const order * ord) case K_KOMMENTAR: return true; default: - return ord->_persistent || is_repeated(ord); + return ord->_persistent || is_repeated(kwd); } } diff --git a/src/kernel/order.h b/src/kernel/order.h index 27c61681c..75d741e42 100644 --- a/src/kernel/order.h +++ b/src/kernel/order.h @@ -55,8 +55,8 @@ extern "C" { char* get_command(const order *ord, char *buffer, size_t size); bool is_persistent(const order * ord); bool is_exclusive(const order * ord); - bool is_repeated(const order * ord); - bool is_long(const order * ord); + bool is_repeated(keyword_t kwd); + bool is_long(keyword_t kwd); char *write_order(const order * ord, char *buffer, size_t size); keyword_t init_order(const struct order *ord); diff --git a/src/kernel/save.c b/src/kernel/save.c index d997c3216..1d610d013 100644 --- a/src/kernel/save.c +++ b/src/kernel/save.c @@ -139,7 +139,8 @@ static unit *unitorders(FILE * F, int enc, struct faction *f) ordp = &u->old_orders; while (*ordp) { order *ord = *ordp; - if (!is_repeated(ord)) { + keyword_t kwd = getkeyword(ord); + if (!is_repeated(kwd)) { *ordp = ord->next; ord->next = NULL; free_order(ord); @@ -233,7 +234,7 @@ static faction *factionorders(void) f->no, pass)); return 0; } - /* Die Partei hat sich zumindest gemeldet, so daß sie noch + /* Die Partei hat sich zumindest gemeldet, so dass sie noch * nicht als untätig gilt */ /* TODO: +1 ist ein Workaround, weil cturn erst in process_orders @@ -309,8 +310,8 @@ int readorders(const char *filename) * Partei, eine neue Einheit oder das File-Ende. Das switch() wird erneut * durchlaufen, und die entsprechende Funktion aufgerufen. Man darf buf * auf alle Fälle nicht überschreiben! Bei allen anderen Einträgen hier - * muß buf erneut gefüllt werden, da die betreffende Information in nur - * einer Zeile steht, und nun die nächste gelesen werden muß. */ + * muss buf erneut gefüllt werden, da die betreffende Information in nur + * einer Zeile steht, und nun die nächste gelesen werden muss. */ case P_NEXT: f = NULL; @@ -777,7 +778,8 @@ void write_unit(struct gamedata *data, const unit * u) } } for (ord = u->orders; ord; ord = ord->next) { - if (u->old_orders && is_repeated(ord)) + keyword_t kwd = getkeyword(ord); + if (u->old_orders && is_repeated(kwd)) continue; /* has new defaults */ if (is_persistent(ord)) { if (++p < MAXPERSISTENT) { @@ -1603,7 +1605,6 @@ int readgame(const char *filename, bool backup) while (--p >= 0) { unit *u = read_unit(&gdata); - sc_mage *mage; if (gdata.version < JSON_REPORT_VERSION) { if (u->_name && fval(u->faction, FFL_NPC)) { @@ -1618,21 +1619,6 @@ int readgame(const char *filename, bool backup) up = &u->next; update_interval(u->faction, u->region); - mage = get_mage(u); - if (mage) { - faction *f = u->faction; - int skl = effskill(u, SK_MAGIC); - if (!fval(f, FFL_NPC) && f->magiegebiet == M_GRAY) { - log_error("faction %s had magic=gray, fixing (%s)\n", factionname(f), magic_school[mage->magietyp]); - f->magiegebiet = mage->magietyp; - } - if (f->max_spelllevel < skl) { - f->max_spelllevel = skl; - } - if (mage->spellcount < 0) { - mage->spellcount = 0; - } - } } } log_printf(stdout, "\n"); @@ -1656,6 +1642,7 @@ int readgame(const char *filename, bool backup) for (f = factions; f; f = f->next) { if (f->flags & FFL_NPC) { f->alive = 1; + f->magiegebiet = M_GRAY; if (f->no == 0) { int no = 666; while (findfaction(no)) @@ -1666,8 +1653,23 @@ int readgame(const char *filename, bool backup) } else { for (u = f->units; u; u = u->nextF) { + sc_mage *mage = get_mage(u); + if (mage) { + faction *f = u->faction; + int skl = effskill(u, SK_MAGIC); + if (f->magiegebiet == M_GRAY) { + log_error("faction %s had magic=gray, fixing (%s)\n", factionname(f), magic_school[mage->magietyp]); + f->magiegebiet = mage->magietyp; + } + if (f->max_spelllevel < skl) { + f->max_spelllevel = skl; + } + if (mage->spellcount < 0) { + mage->spellcount = 0; + } + } if (u->number > 0) { - f->alive = 1; + f->alive = true; break; } } diff --git a/src/kernel/terrain.c b/src/kernel/terrain.c index 36efb3591..f27fe1bd0 100644 --- a/src/kernel/terrain.c +++ b/src/kernel/terrain.c @@ -127,8 +127,7 @@ const struct terrain_type *newterrain(terrain_t t) terrain_t oldterrain(const struct terrain_type * terrain) { terrain_t t; - if (terrain == NULL) - return NOTERRAIN; + assert(terrain); for (t = 0; t != MAXTERRAINS; ++t) { if (newterrains[t] == terrain) return t; diff --git a/src/kernel/unit.c b/src/kernel/unit.c index a43ae2153..310a407bd 100644 --- a/src/kernel/unit.c +++ b/src/kernel/unit.c @@ -1274,25 +1274,10 @@ static int item_modification(const unit * u, skill_t sk, int val) return val; } -static int update_gbdream(const unit * u, int bonus, curse *c, const curse_type *gbdream_ct, int sign){ - if (curse_active(c) && c->type == gbdream_ct) { - double effect = curse_geteffect(c); - unit *mage = c->magician; - /* wir suchen jeweils den groessten Bonus und den groestsen Malus */ - if (sign * effect > sign * bonus) { - if (mage == NULL || mage->number == 0 - || (sign>0?alliedunit(mage, u->faction, HELP_GUARD):!alliedunit(mage, u->faction, HELP_GUARD))) { - bonus = (int)effect; - } - } - } - return bonus; -} - -int att_modification(const unit * u, skill_t sk) +static int att_modification(const unit * u, skill_t sk) { double result = 0; - static bool init = false; + static bool init = false; // TODO: static variables are bad global state static const curse_type *skillmod_ct, *gbdream_ct, *worse_ct; curse *c; @@ -1321,15 +1306,21 @@ int att_modification(const unit * u, skill_t sk) /* TODO hier kann nicht mit get/iscursed gearbeitet werden, da nur der * jeweils erste vom Typ C_GBDREAM zurueckgegen wird, wir aber alle * durchsuchen und aufaddieren muessen */ - if (u->region) { + if (gbdream_ct && u->region) { int bonus = 0, malus = 0; attrib *a = a_find(u->region->attribs, &at_curse); while (a && a->type == &at_curse) { curse *c = (curse *)a->data.v; - bonus = update_gbdream(u, bonus, c, gbdream_ct, 1); - malus = update_gbdream(u, malus, c, gbdream_ct, -1); - + if (curse_active(c) && c->type == gbdream_ct) { + int effect = curse_geteffect_int(c); + bool allied = alliedunit(c->magician, u->faction, HELP_GUARD); + if (allied) { + if (effect > bonus) bonus = effect; + } else { + if (effect < malus) malus = effect; + } + } a = a->next; } result = result + bonus + malus; @@ -1717,6 +1708,16 @@ int unit_getcapacity(const unit * u) return walkingcapacity(u); } +void renumber_unit(unit *u, int no) { + uunhash(u); + if (!ualias(u)) { + attrib *a = a_add(&u->attribs, a_new(&at_alias)); + a->data.i = -u->no; + } + u->no = no; + uhash(u); +} + void unit_addorder(unit * u, order * ord) { order **ordp = &u->orders; diff --git a/src/kernel/unit.h b/src/kernel/unit.h index c84ccbdfe..631809bf9 100644 --- a/src/kernel/unit.h +++ b/src/kernel/unit.h @@ -127,6 +127,8 @@ extern "C" { int ualias(const struct unit *u); int weight(const struct unit *u); + void renumber_unit(struct unit *u, int no); + const struct race *u_irace(const struct unit *u); const struct race *u_race(const struct unit *u); void u_setrace(struct unit *u, const struct race *); diff --git a/src/laws.c b/src/laws.c index 6a18dc84e..af1347bd1 100755 --- a/src/laws.c +++ b/src/laws.c @@ -183,7 +183,7 @@ static void live(region * r) reduce_skill(u, sb, weeks); ADDMSG(&u->faction->msgs, msg_message("dumbeffect", "unit weeks skill", u, weeks, (skill_t)sb->id)); - } /* sonst Glück gehabt: wer nix weiß, kann nix vergessen... */ + } /* sonst Glück gehabt: wer nix weiss, kann nix vergessen... */ change_effect(u, oldpotiontype[P_FOOL], -effect); } age_unit(r, u); @@ -402,7 +402,7 @@ static void migrate(region * r) * wer fragt das? Die Baumwanderung war abhängig von der * Auswertungsreihenfolge der regionen, * das hatte ich geändert. jemand hat es wieder gelöscht, toll. - * ich habe es wieder aktiviert, muß getestet werden. + * ich habe es wieder aktiviert, muss getestet werden. */ *hp = m->next; m->next = free_migrants; @@ -442,7 +442,7 @@ static void horses(region * r) /* Pferde wandern in Nachbarregionen. * Falls die Nachbarregion noch berechnet - * werden muß, wird eine migration-Struktur gebildet, + * werden muss, wird eine migration-Struktur gebildet, * die dann erst in die Berechnung der Nachbarstruktur einfließt. */ @@ -632,7 +632,7 @@ growing_trees(region * r, const int current_season, const int last_weeks_season) a->data.sa[0] = (short)rtrees(r, 0); a->data.sa[1] = (short)rtrees(r, 1); } - /* wir haben 6 Wochen zum wachsen, jeder Same/Sproß hat 18% Chance + /* wir haben 6 Wochen zum wachsen, jeder Same/Spross hat 18% Chance * zu wachsen, damit sollten nach 5-6 Wochen alle gewachsen sein */ growth = 1800; @@ -1347,10 +1347,12 @@ int ally_cmd(unit * u, struct order *ord) s = gettoken(token, sizeof(token)); - if (s && !s[0]) + if (!s || !s[0]) { keyword = P_ANY; - else + } + else { keyword = findparam(s, u->faction->locale); + } sfp = &u->faction->allies; if (fval(u, UFL_GROUP)) { @@ -1990,7 +1992,7 @@ int mail_cmd(unit * u, struct order *ord) s = gettoken(token, sizeof(token)); /* Falls kein Parameter, ist das eine Einheitsnummer; - * das Füllwort "AN" muß wegfallen, da gültige Nummer! */ + * das Füllwort "AN" muss wegfallen, da gültige Nummer! */ do { cont = 0; @@ -3020,13 +3022,7 @@ int renumber_cmd(unit * u, order * ord) break; } } - uunhash(u); - if (!ualias(u)) { - attrib *a = a_add(&u->attribs, a_new(&at_alias)); - a->data.i = -u->no; - } - u->no = i; - uhash(u); + renumber_unit(u, i); break; case P_SHIP: @@ -3407,126 +3403,101 @@ void new_units(void) } } -/** Checks for two long orders and issues a warning if necessary. - */ -void check_long_orders(unit * u) +void update_long_order(unit * u) { order *ord; - keyword_t otherorder = MAXKEYWORDS; + bool exclusive = true; + keyword_t thiskwd = NOKEYWORD; + bool hunger = LongHunger(u); + freset(u, UFL_MOVED); + freset(u, UFL_LONGACTION); + + /* check all orders for a potential new long order this round: */ for (ord = u->orders; ord; ord = ord->next) { - if (getkeyword(ord) == NOKEYWORD) { - cmistake(u, ord, 22, MSG_EVENT); + keyword_t kwd = getkeyword(ord); + if (kwd == NOKEYWORD) continue; + + if (u->old_orders && is_repeated(kwd)) { + /* this new order will replace the old defaults */ + free_orders(&u->old_orders); } - else if (is_long(ord)) { - keyword_t longorder = getkeyword(ord); - if (otherorder != MAXKEYWORDS) { - switch (longorder) { + + // hungry units do not get long orders: + if (hunger) { + if (u->old_orders) { + // keep looking for repeated orders that might clear the old_orders + continue; + } + break; + } + + if (is_long(kwd)) { + if (thiskwd == NOKEYWORD) { + // we have found the (first) long order + // some long orders can have multiple instances: + switch (kwd) { + /* Wenn gehandelt wird, darf kein langer Befehl ausgeführt + * werden. Da Handel erst nach anderen langen Befehlen kommt, + * muss das vorher abgefangen werden. Wir merken uns also + * hier, ob die Einheit handelt. */ + case K_BUY: + case K_SELL: case K_CAST: - if (otherorder != longorder) { + // non-exclusive orders can be used with others. BUY can be paired with SELL, + // CAST with other CAST orders. compatibility is checked once the second + // long order is analyzed (below). + exclusive = false; + break; + + default: + set_order(&u->thisorder, copy_order(ord)); + break; + } + thiskwd = kwd; + } + else { + // we have found a second long order. this is okay for some, but not all commands. + // u->thisorder is already set, and should not have to be updated. + switch (kwd) { + case K_CAST: + if (thiskwd != K_CAST) { + cmistake(u, ord, 52, MSG_EVENT); + } + break; + case K_SELL: + if (thiskwd != K_SELL && thiskwd != K_BUY) { cmistake(u, ord, 52, MSG_EVENT); } break; case K_BUY: - if (otherorder == K_SELL) { - otherorder = K_BUY; + if (thiskwd != K_SELL) { + cmistake(u, ord, 52, MSG_EVENT); + } + else { + thiskwd = K_BUY; + } + break; + default: + // TODO: decide https://bugs.eressea.de/view.php?id=2080#c6011 + if (kwd > thiskwd) { + // swap out thisorder for the new one + cmistake(u, u->thisorder, 52, MSG_EVENT); + set_order(&u->thisorder, copy_order(ord)); } else { cmistake(u, ord, 52, MSG_EVENT); } break; - case K_SELL: - if (otherorder != K_SELL && otherorder != K_BUY) { - cmistake(u, ord, 52, MSG_EVENT); - } - break; - default: - cmistake(u, ord, 52, MSG_EVENT); } } - else { - otherorder = longorder; - } } } -} - -void update_long_order(unit * u) -{ - order *ord; - bool trade = false; - bool hunger = LongHunger(u); - - freset(u, UFL_MOVED); - freset(u, UFL_LONGACTION); if (hunger) { - /* Hungernde Einheiten führen NUR den default-Befehl aus */ + // Hungernde Einheiten führen NUR den default-Befehl aus set_order(&u->thisorder, default_order(u->faction->locale)); - } - else { - check_long_orders(u); - } - /* check all orders for a potential new long order this round: */ - for (ord = u->orders; ord; ord = ord->next) { - if (getkeyword(ord) == NOKEYWORD) - continue; - - if (u->old_orders && is_repeated(ord)) { - /* this new order will replace the old defaults */ - free_orders(&u->old_orders); - if (hunger) - break; - } - if (hunger) - continue; - - if (is_exclusive(ord)) { - /* Ãœber dieser Zeile nur Befehle, die auch eine idle Faction machen darf */ - if (idle(u->faction)) { - set_order(&u->thisorder, default_order(u->faction->locale)); - } - else { - set_order(&u->thisorder, copy_order(ord)); - } - break; - } - else { - keyword_t keyword = getkeyword(ord); - switch (keyword) { - /* Wenn gehandelt wird, darf kein langer Befehl ausgeführt - * werden. Da Handel erst nach anderen langen Befehlen kommt, - * muß das vorher abgefangen werden. Wir merken uns also - * hier, ob die Einheit handelt. */ - case K_BUY: - case K_SELL: - /* Wenn die Einheit handelt, muß der Default-Befehl gelöscht - * werden. - * Wird je diese Ausschliesslichkeit aufgehoben, muss man aufpassen - * mit der Reihenfolge von Kaufen, Verkaufen etc., damit es Spielern - * nicht moeglich ist, Schulden zu machen. */ - trade = true; - break; - - case K_CAST: - /* dient dazu, das neben Zaubern kein weiterer Befehl - * ausgeführt werden kann, Zaubern ist ein kurzer Befehl */ - set_order(&u->thisorder, copy_order(ord)); - break; - - default: - break; - } - } - } - - if (hunger) { - return; - } - /* Wenn die Einheit handelt, muß der Default-Befehl gelöscht - * werden. */ - - if (trade) { - /* fset(u, UFL_LONGACTION|UFL_NOTMOVING); */ + } else if (!exclusive) { + // Wenn die Einheit handelt oder zaubert, muss der Default-Befehl gelöscht werden. set_order(&u->thisorder, NULL); } } @@ -3696,6 +3667,7 @@ void defaultorders(void) free_order(ord); if (!neworders) { /* lange Befehle aus orders und old_orders löschen zu gunsten des neuen */ + // TODO: why only is_exclusive, not is_long? what about CAST, BUY, SELL? remove_exclusive(&u->orders); remove_exclusive(&u->old_orders); neworders = true; @@ -4489,7 +4461,7 @@ void init_processor(void) add_proc_order(p, K_TEACH, teach_cmd, PROC_THISORDER | PROC_LONGORDER, "Lehren"); p += 10; - add_proc_order(p, K_STUDY, learn_cmd, PROC_THISORDER | PROC_LONGORDER, + add_proc_order(p, K_STUDY, study_cmd, PROC_THISORDER | PROC_LONGORDER, "Lernen"); p += 10; diff --git a/src/laws.h b/src/laws.h index 7eec612e3..987e3d2b0 100755 --- a/src/laws.h +++ b/src/laws.h @@ -56,15 +56,15 @@ extern "C" { extern int *age; - extern void new_units(void); - extern void defaultorders(void); - extern void quit(void); - extern void monthly_healing(void); - extern void renumber_factions(void); - extern void restack_units(void); - extern void update_long_order(struct unit *u); - extern void sinkships(struct region * r); - extern void do_enter(struct region *r, bool is_final_attempt); + void new_units(void); + void defaultorders(void); + void quit(void); + void monthly_healing(void); + void renumber_factions(void); + void restack_units(void); + void update_long_order(struct unit *u); + void sinkships(struct region * r); + void do_enter(struct region *r, bool is_final_attempt); extern int password_cmd(struct unit *u, struct order *ord); extern int banner_cmd(struct unit *u, struct order *ord); diff --git a/src/laws.test.c b/src/laws.test.c index b5429e1aa..0c4881c85 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -1,6 +1,7 @@ #include #include "laws.h" #include "battle.h" +#include "monster.h" #include #include @@ -768,9 +769,225 @@ static void test_luck_message(CuTest *tc) { test_cleanup(); } +static void test_long_order_normal(CuTest *tc) { + // TODO: write more tests + unit *u; + order *ord; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + fset(u, UFL_MOVED); + fset(u, UFL_LONGACTION); + u->faction->locale = get_or_create_locale("de"); + ord = create_order(K_MOVE, u->faction->locale, 0); + unit_addorder(u, ord); + update_long_order(u); + CuAssertPtrEquals(tc, ord->data, u->thisorder->data); + CuAssertIntEquals(tc, 0, fval(u, UFL_MOVED)); + CuAssertIntEquals(tc, 0, fval(u, UFL_LONGACTION)); + CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + CuAssertPtrEquals(tc, 0, u->old_orders); + test_cleanup(); +} + +static void test_long_order_none(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + update_long_order(u); + CuAssertPtrEquals(tc, 0, u->thisorder); + CuAssertPtrEquals(tc, 0, u->orders); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + test_cleanup(); +} + +static void test_long_order_cast(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_CAST, u->faction->locale, 0)); + unit_addorder(u, create_order(K_CAST, u->faction->locale, 0)); + update_long_order(u); + CuAssertPtrEquals(tc, 0, u->thisorder); + CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + test_cleanup(); +} + +static void test_long_order_buy_sell(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + unit_addorder(u, create_order(K_SELL, u->faction->locale, 0)); + unit_addorder(u, create_order(K_SELL, u->faction->locale, 0)); + update_long_order(u); + CuAssertPtrEquals(tc, 0, u->thisorder); + CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + test_cleanup(); +} + +static void test_long_order_multi_long(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_MOVE, u->faction->locale, 0)); + unit_addorder(u, create_order(K_DESTROY, u->faction->locale, 0)); + update_long_order(u); + CuAssertPtrNotNull(tc, u->thisorder); + CuAssertPtrNotNull(tc, u->orders); + CuAssertStrEquals(tc, "error52", test_get_messagetype(u->faction->msgs->begin->msg)); + test_cleanup(); +} + +static void test_long_order_multi_buy(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + update_long_order(u); + CuAssertPtrEquals(tc, 0, u->thisorder); + CuAssertPtrNotNull(tc, u->orders); + CuAssertStrEquals(tc, "error52", test_get_messagetype(u->faction->msgs->begin->msg)); + test_cleanup(); +} + +static void test_long_order_multi_sell(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_SELL, u->faction->locale, 0)); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + unit_addorder(u, create_order(K_SELL, u->faction->locale, 0)); + update_long_order(u); + CuAssertPtrEquals(tc, 0, u->thisorder); + CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + test_cleanup(); +} + +static void test_long_order_buy_cast(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + unit_addorder(u, create_order(K_CAST, u->faction->locale, 0)); + update_long_order(u); + CuAssertPtrEquals(tc, 0, u->thisorder); + CuAssertPtrNotNull(tc, u->orders); + CuAssertStrEquals(tc, "error52", test_get_messagetype(u->faction->msgs->begin->msg)); + test_cleanup(); +} + +static void test_long_order_hungry(CuTest *tc) { + // TODO: write more tests + unit *u; + test_cleanup(); + set_param(&global.parameters, "hunger.long", "1"); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + fset(u, UFL_HUNGER); + u->faction->locale = get_or_create_locale("de"); + unit_addorder(u, create_order(K_MOVE, u->faction->locale, 0)); + unit_addorder(u, create_order(K_DESTROY, u->faction->locale, 0)); + set_default_order(K_WORK); + update_long_order(u); + CuAssertIntEquals(tc, K_WORK, getkeyword(u->thisorder)); + CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + set_default_order(NOKEYWORD); + test_cleanup(); +} + +static void test_ally_cmd_errors(CuTest *tc) { + unit *u; + int fid; + order *ord; + + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = get_or_create_locale("de"); + fid = u->faction->no + 1; + CuAssertPtrEquals(tc, 0, findfaction(fid)); + + ord = create_order(K_ALLY, u->faction->locale, itoa36(fid)); + ally_cmd(u, ord); + CuAssertStrEquals(tc, "error66", test_get_messagetype(u->faction->msgs->begin->msg)); + free_order(ord); + + test_cleanup(); +} + +static void test_ally_cmd(CuTest *tc) { + unit *u; + faction * f; + order *ord; + struct locale *lang; + + test_cleanup(); + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + f = test_create_faction(0); + u->faction->locale = lang = get_or_create_locale("de"); + locale_setstring(lang, parameters[P_NOT], "NICHT"); + locale_setstring(lang, parameters[P_GUARD], "BEWACHE"); + init_parameters(lang); + + ord = create_order(K_ALLY, lang, "%s", itoa36(f->no)); + ally_cmd(u, ord); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + CuAssertIntEquals(tc, HELP_ALL, alliedfaction(0, u->faction, f, HELP_ALL)); + free_order(ord); + + ord = create_order(K_ALLY, lang, "%s NICHT", itoa36(f->no)); + ally_cmd(u, ord); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + CuAssertIntEquals(tc, 0, alliedfaction(0, u->faction, f, HELP_ALL)); + free_order(ord); + + ord = create_order(K_ALLY, lang, "%s BEWACHE", itoa36(f->no)); + ally_cmd(u, ord); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + CuAssertIntEquals(tc, HELP_GUARD, alliedfaction(0, u->faction, f, HELP_ALL)); + free_order(ord); + + ord = create_order(K_ALLY, lang, "%s BEWACHE NICHT", itoa36(f->no)); + ally_cmd(u, ord); + CuAssertPtrEquals(tc, 0, u->faction->msgs); + CuAssertIntEquals(tc, 0, alliedfaction(0, u->faction, f, HELP_ALL)); + free_order(ord); + + test_cleanup(); +} + CuSuite *get_laws_suite(void) { CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_ally_cmd); + SUITE_ADD_TEST(suite, test_ally_cmd_errors); + SUITE_ADD_TEST(suite, test_long_order_normal); + SUITE_ADD_TEST(suite, test_long_order_none); + SUITE_ADD_TEST(suite, test_long_order_cast); + SUITE_ADD_TEST(suite, test_long_order_buy_sell); + SUITE_ADD_TEST(suite, test_long_order_multi_long); + SUITE_ADD_TEST(suite, test_long_order_multi_buy); + SUITE_ADD_TEST(suite, test_long_order_multi_sell); + SUITE_ADD_TEST(suite, test_long_order_buy_cast); + SUITE_ADD_TEST(suite, test_long_order_hungry); SUITE_ADD_TEST(suite, test_new_building_can_be_renamed); SUITE_ADD_TEST(suite, test_rename_building); SUITE_ADD_TEST(suite, test_rename_building_twice); diff --git a/src/magic.c b/src/magic.c index ab5b405f8..1221a7c95 100644 --- a/src/magic.c +++ b/src/magic.c @@ -777,7 +777,7 @@ int spellcost(unit * u, const spell * sp) int count = countspells(u, 0); const resource_type *r_aura = get_resourcetype(R_AURA); - for (k = 0; sp->components[k].type; k++) { + for (k = 0; sp->components && sp->components[k].type; k++) { if (sp->components[k].type == r_aura) { aura = sp->components[k].amount; } @@ -798,7 +798,7 @@ static int spl_costtyp(const spell * sp) int k; int costtyp = SPC_FIX; - for (k = 0; sp->components[k].type; k++) { + for (k = 0; sp->components && sp->components[k].type; k++) { if (costtyp == SPC_LINEAR) return SPC_LINEAR; @@ -827,7 +827,7 @@ int eff_spelllevel(unit * u, const spell * sp, int cast_level, int range) int k, maxlevel, needplevel; int costtyp = SPC_FIX; - for (k = 0; sp->components[k].type; k++) { + for (k = 0; sp->components && sp->components[k].type; k++) { if (cast_level == 0) return 0; @@ -894,7 +894,7 @@ void pay_spell(unit * u, const spell * sp, int cast_level, int range) int resuse; assert(cast_level > 0); - for (k = 0; sp->components[k].type; k++) { + for (k = 0; sp->components && sp->components[k].type; k++) { if (sp->components[k].type == r_aura) { resuse = spellcost(u, sp) * range; } @@ -954,7 +954,7 @@ cancast(unit * u, const spell * sp, int level, int range, struct order * ord) return false; } - for (k = 0; sp->components[k].type; ++k) { + for (k = 0; sp->components && sp->components[k].type; ++k) { if (sp->components[k].amount > 0) { const resource_type *rtype = sp->components[k].type; int itemhave; @@ -2766,15 +2766,13 @@ void magic(void) continue; } - if (u->thisorder != NULL) { - for (ord = u->orders; ord; ord = ord->next) { - if (getkeyword(ord) == K_CAST) { - castorder *co = cast_cmd(u, ord); - fset(u, UFL_LONGACTION | UFL_NOTMOVING); - if (co) { - const spell *sp = co->sp; - add_castorder(&spellranks[sp->rank], co); - } + for (ord = u->orders; ord; ord = ord->next) { + if (getkeyword(ord) == K_CAST) { + castorder *co = cast_cmd(u, ord); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + if (co) { + const spell *sp = co->sp; + add_castorder(&spellranks[sp->rank], co); } } } diff --git a/src/magic.test.c b/src/magic.test.c index 9a01d5d4e..600f724ca 100644 --- a/src/magic.test.c +++ b/src/magic.test.c @@ -3,6 +3,7 @@ #include "magic.h" #include +#include #include #include #include @@ -382,9 +383,45 @@ void test_hasspell(CuTest * tc) test_cleanup(); } +static quicklist * casts; + +static int cast_fireball(struct castorder * co) { + ql_push(&casts, co); + return 0; +} + +void test_multi_cast(CuTest *tc) { + unit *u; + spell *sp; + struct locale * lang; + + test_cleanup(); + sp = create_spell("fireball", 0); + sp->cast = cast_fireball; + CuAssertPtrEquals(tc, sp, find_spell("fireball")); + + u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + u->faction->locale = lang = get_or_create_locale("de"); + locale_setstring(lang, mkname("spell", sp->sname), "Feuerball"); + CuAssertStrEquals(tc, "Feuerball", spell_name(sp, lang)); + set_level(u, SK_MAGIC, 10); + unit_add_spell(u, 0, sp, 1); + CuAssertPtrEquals(tc, sp, unit_getspell(u, "Feuerball", lang)); + + unit_addorder(u, create_order(K_CAST, u->faction->locale, "Feuerball")); + unit_addorder(u, create_order(K_CAST, u->faction->locale, "Feuerball")); + CuAssertPtrEquals(tc, casts, 0); + magic(); + CuAssertPtrNotNull(tc, casts); + CuAssertIntEquals(tc, 2, ql_length(casts)); + ql_free(casts); + test_cleanup(); +} + CuSuite *get_magic_suite(void) { CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_multi_cast); SUITE_ADD_TEST(suite, test_updatespells); SUITE_ADD_TEST(suite, test_spellbooks); SUITE_ADD_TEST(suite, test_pay_spell); diff --git a/src/move.c b/src/move.c index f29dd08ce..a9ed36b3e 100644 --- a/src/move.c +++ b/src/move.c @@ -366,7 +366,7 @@ static int canwalk(unit * u) return E_CANWALK_TOOMANYCARTS; /* Es muß nicht zwingend an den Wagen liegen, aber egal... (man * könnte z.B. auch 8 Eisen abladen, damit ein weiterer Wagen als - * Fracht draufpaßt) */ + * Fracht draufpasst) */ return E_CANWALK_TOOHEAVY; } diff --git a/src/report.c b/src/report.c index 0b591b5ed..3d57ee4c2 100644 --- a/src/report.c +++ b/src/report.c @@ -1471,14 +1471,12 @@ report_template(const char *filename, report_context * ctx, const char *charset) newline(out); newline(out); - sprintf(buf, "%s %s \"%s\"", LOC(f->locale, "ERESSEA"), factionid(f), - LOC(f->locale, "enterpasswd")); + sprintf(buf, "%s %s \"%s\"", LOC(f->locale, "ERESSEA"), factionid(f), f->passw); rps_nowrap(out, buf); newline(out); newline(out); sprintf(buf, "; ECHECK -l -w4 -r%d -v%s", f->race->recruitcost, ECHECK_VERSION); - /* -v3.4: ECheck Version 3.4.x */ rps_nowrap(out, buf); newline(out); @@ -1575,7 +1573,8 @@ report_template(const char *filename, report_context * ctx, const char *charset) newline(out); } for (ord = u->orders; ord; ord = ord->next) { - if (u->old_orders && is_repeated(ord)) + keyword_t kwd = getkeyword(ord); + if (u->old_orders && is_repeated(kwd)) continue; /* unit has defaults */ if (is_persistent(ord)) { strcpy(buf, " "); diff --git a/src/reports.c b/src/reports.c index 128815f71..639b859bf 100644 --- a/src/reports.c +++ b/src/reports.c @@ -52,6 +52,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include +#include #include /* libc includes */ @@ -59,6 +60,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include +#include #include #include @@ -793,7 +795,8 @@ size_t size) bool printed = 0; order *ord;; for (ord = u->old_orders; ord; ord = ord->next) { - if (is_repeated(ord)) { + keyword_t kwd = getkeyword(ord); + if (is_repeated(kwd)) { if (printed < ORDERS_IN_NR) { bytes = buforder(bufp, size, ord, printed++); if (wrptr(&bufp, &size, bytes) != 0) @@ -805,7 +808,8 @@ size_t size) } if (printed < ORDERS_IN_NR) for (ord = u->orders; ord; ord = ord->next) { - if (is_repeated(ord)) { + keyword_t kwd = getkeyword(ord); + if (is_repeated(kwd)) { if (printed < ORDERS_IN_NR) { bytes = buforder(bufp, size, ord, printed++); if (wrptr(&bufp, &size, bytes) != 0) @@ -2291,7 +2295,7 @@ static void eval_race(struct opstack **stack, const void *userdata) static void eval_order(struct opstack **stack, const void *userdata) { /* order -> string */ const struct order *ord = (const struct order *)opop(stack).v; - char buf[512]; + char buf[4096]; size_t len; variant var; @@ -2497,6 +2501,23 @@ static void log_orders(const struct message *msg) } } +int stream_printf(struct stream * out, const char *format, ...) { + va_list args; + int result; + char buffer[4096]; + size_t bytes = sizeof(buffer); + // TODO: should be in storage/stream.c (doesn't exist yet) + va_start(args, format); + result = vsnprintf(buffer, bytes, format, args); + if (result >= 0 && (size_t)result < bytes) { + bytes = (size_t)result; + // TODO: else = buffer too small + } + out->api->write(out->handle, buffer, bytes); + va_end(args); + return result; +} + void register_reports(void) { /* register datatypes for the different message objects */ diff --git a/src/reports.h b/src/reports.h index 93b3ab122..ed0687985 100644 --- a/src/reports.h +++ b/src/reports.h @@ -25,6 +25,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +struct stream; + #ifdef __cplusplus extern "C" { #endif @@ -153,6 +155,7 @@ extern "C" { void freestrlist(strlist * s); void split_paragraph(strlist ** SP, const char *s, unsigned int indent, unsigned int width, char mark); + int stream_printf(struct stream * out, const char *format, ...); #define GR_PLURAL 0x01 /* grammar: plural */ #define MAX_INVENTORY 128 /* maimum number of different items in an inventory */ diff --git a/src/reports.test.c b/src/reports.test.c index 9dd9f0913..89bca6e05 100644 --- a/src/reports.test.c +++ b/src/reports.test.c @@ -2,6 +2,7 @@ #include #include "reports.h" #include "report.h" +#include "creport.h" #include #include @@ -155,9 +156,32 @@ static void test_sparagraph(CuTest *tc) { CuAssertPtrEquals(tc, 0, sp->next->next->next); } +static void test_cr_unit(CuTest *tc) { + stream strm; + char line[1024]; + faction *f; + region *r; + unit *u; + + test_cleanup(); + f = test_create_faction(0); + r = test_create_region(0, 0, 0); + u = test_create_unit(f, r); + renumber_unit(u, 1234); + + mstream_init(&strm); + cr_output_unit(&strm, r, f, u, see_unit); + strm.api->rewind(strm.handle); + CuAssertIntEquals(tc, 0, strm.api->readln(strm.handle, line, sizeof(line))); + CuAssertStrEquals(tc, line, "EINHEIT 1234"); + mstream_done(&strm); + test_cleanup(); +} + CuSuite *get_reports_suite(void) { CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_cr_unit); SUITE_ADD_TEST(suite, test_reorder_units); SUITE_ADD_TEST(suite, test_seen_faction); SUITE_ADD_TEST(suite, test_regionid); diff --git a/src/spells.test.c b/src/spells.test.c index ff67dd58d..5ecb8bd88 100644 --- a/src/spells.test.c +++ b/src/spells.test.c @@ -20,13 +20,10 @@ #include -static struct castorder *test_create_castorder(castorder *order, unit *u, const char *name, int level, float force, int range) { +static void test_create_castorder(castorder *order, unit *u, int level, float force, int range) { struct locale * lang; - spell *sp; - lang = get_or_create_locale("en"); - sp = create_spell(name, 0); - return order = create_castorder(order, u, NULL, sp, u->region, level, force, range, create_order(K_CAST, lang, ""), NULL); + create_castorder(order, u, NULL, NULL, u->region, level, force, range, create_order(K_CAST, lang, ""), NULL); } static void test_dreams(CuTest *tc) { @@ -39,12 +36,12 @@ static void test_dreams(CuTest *tc) { test_cleanup(); test_create_world(); r=findregion(0, 0); - f1 = test_create_faction(test_create_race("human")); - f2 = test_create_faction(test_create_race("human")); + f1 = test_create_faction(0); + f2 = test_create_faction(0); u1 = test_create_unit(f1, r); u2 = test_create_unit(f2, r); - test_create_castorder(&order, u1, "goodreams", 10, 10., 0); + test_create_castorder(&order, u1, 10, 10., 0); level = sp_gooddreams(&order); CuAssertIntEquals(tc, 10, level); @@ -57,7 +54,7 @@ static void test_dreams(CuTest *tc) { CuAssertIntEquals(tc, 1, get_modifier(u1, SK_MELEE, 11, r, false)); CuAssertIntEquals(tc, 0, get_modifier(u2, SK_MELEE, 11, r, false)); - test_create_castorder(&order, u1, "baddreams", 10, 10., 0); + test_create_castorder(&order, u1, 10, 10., 0); level = sp_baddreams(&order); CuAssertIntEquals(tc, 10, level); diff --git a/src/sqlite.c b/src/sqlite.c index c19aa6903..fd46c4f1f 100644 --- a/src/sqlite.c +++ b/src/sqlite.c @@ -123,9 +123,9 @@ static void update_faction(sqlite3 *db, const faction *f) { "INSERT INTO faction_data (faction_id, code, name, email, lang, turn)" " VALUES (?, ?, ?, ?, ?, ?)"; sqlite3_stmt *stmt = 0; + strcpy(code, itoa36(f->no)); sqlite3_prepare_v2(db, sql, -1, &stmt, 0); sqlite3_bind_int(stmt, 1, f->subscription); - strcpy(code, itoa36(f->no)); sqlite3_bind_text(stmt, 2, code, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, f->name, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 4, f->email, -1, SQLITE_STATIC); diff --git a/src/study.c b/src/study.c index ec377f0f7..08bb749b2 100644 --- a/src/study.c +++ b/src/study.c @@ -222,7 +222,7 @@ bool report, int *academy) teach->teachers[index] = NULL; } else { - log_warning("MAXTEACHERS is too low at %d", MAXTEACHERS); + log_error("MAXTEACHERS=%d is too low for student %s, teacher %s", MAXTEACHERS, unitname(student), unitname(teacher)); } teach->value += n; @@ -527,7 +527,7 @@ static double study_speedup(unit * u, skill_t s, study_rule_t rule) return 1.0; } -int learn_cmd(unit * u, order * ord) +int study_cmd(unit * u, order * ord) { region *r = u->region; int p; diff --git a/src/study.h b/src/study.h index d5d664fd2..1feb55921 100644 --- a/src/study.h +++ b/src/study.h @@ -20,19 +20,20 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #define H_KRNL_STUDY #include "skill.h" +#include #ifdef __cplusplus extern "C" { #endif extern int teach_cmd(struct unit *u, struct order *ord); - extern int learn_cmd(struct unit *u, struct order *ord); + extern int study_cmd(struct unit *u, struct order *ord); extern magic_t getmagicskill(const struct locale *lang); extern bool is_migrant(struct unit *u); extern int study_cost(struct unit *u, skill_t talent); -#define MAXTEACHERS 16 +#define MAXTEACHERS 20 typedef struct teaching_info { struct unit *teachers[MAXTEACHERS]; int value; diff --git a/src/study.test.c b/src/study.test.c new file mode 100644 index 000000000..58614d37c --- /dev/null +++ b/src/study.test.c @@ -0,0 +1,99 @@ +#include + +#include "study.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +typedef struct { + unit *u; + unit *teachers[2]; +} study_fixture; + +static void setup_study(study_fixture *fix, skill_t sk) { + struct region * r; + struct faction *f; + struct locale *lang; + + assert(fix); + test_cleanup(); + test_create_world(); + r = test_create_region(0, 0, 0); + f = test_create_faction(0); + lang = get_or_create_locale(locale_name(f->locale)); + locale_setstring(lang, mkname("skill", skillnames[sk]), skillnames[sk]); + init_skills(lang); + fix->u = test_create_unit(f, r); + assert(fix->u); + fix->u->thisorder = create_order(K_STUDY, f->locale, "%s", 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[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)); +} + +static void test_study_no_teacher(CuTest *tc) { + study_fixture fix; + skill *sv; + + setup_study(&fix, SK_CROSSBOW); + study_cmd(fix.u, fix.u->thisorder); + 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)); + test_cleanup(); +} + +static void test_study_with_teacher(CuTest *tc) { + study_fixture fix; + skill *sv; + + setup_study(&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)); + study_cmd(fix.u, fix.u->thisorder); + CuAssertPtrNotNull(tc, sv = unit_skill(fix.u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 1, sv->weeks); + test_cleanup(); +} + +static void test_study_with_bad_teacher(CuTest *tc) { + study_fixture fix; + skill *sv; + message *msg; + + setup_study(&fix, SK_CROSSBOW); + teach_cmd(fix.teachers[0], fix.teachers[0]->thisorder); + CuAssertPtrNotNull(tc, msg = test_get_last_message(fix.u->faction->msgs)); + CuAssertStrEquals(tc, "teach_asgood", test_get_messagetype(msg)); + study_cmd(fix.u, fix.u->thisorder); + CuAssertPtrNotNull(tc, sv = unit_skill(fix.u, SK_CROSSBOW)); + CuAssertIntEquals(tc, 1, sv->level); + CuAssertIntEquals(tc, 2, sv->weeks); + test_cleanup(); +} + +CuSuite *get_study_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_study_no_teacher); + SUITE_ADD_TEST(suite, test_study_with_teacher); + SUITE_ADD_TEST(suite, test_study_with_bad_teacher); + return suite; +} diff --git a/src/test_eressea.c b/src/test_eressea.c index b5a2e2a42..e434f6c7f 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -89,6 +89,7 @@ int RunAllTests(void) RUN_TESTS(suite, vortex); RUN_TESTS(suite, wormhole); RUN_TESTS(suite, spy); + RUN_TESTS(suite, study); printf("\ntest summary: %d tests, %d failed\n", suite->count, suite->failCount); log_flags = flags; diff --git a/src/tests.c b/src/tests.c index 9c03e1e18..5a19ce9c0 100644 --- a/src/tests.c +++ b/src/tests.c @@ -209,15 +209,20 @@ void test_create_world(void) } message * test_get_last_message(message_list *msgs) { - struct mlist *iter = msgs->begin; - while (iter->next) { - iter = iter->next; + if (msgs) { + struct mlist *iter = msgs->begin; + while (iter->next) { + iter = iter->next; + } + return iter->msg; } - return iter->msg; + return 0; } const char * test_get_messagetype(const message *msg) { - const char * name = msg->type->name; + const char * name; + assert(msg); + name = msg->type->name; if (strcmp(name, "missing_message") == 0) { name = (const char *)msg->parameters[0].v; } diff --git a/src/util/strings.c b/src/util/strings.c index 3f3d7dc36..b3801d52f 100644 --- a/src/util/strings.c +++ b/src/util/strings.c @@ -43,6 +43,7 @@ char *set_string(char **s, const char *neu) unsigned int hashstring(const char *s) { unsigned int key = 0; + assert(s); while (*s) { key = key * 37 + *s++; } diff --git a/src/util/translation.c b/src/util/translation.c index d73074294..16d3e684f 100644 --- a/src/util/translation.c +++ b/src/util/translation.c @@ -31,7 +31,7 @@ typedef struct opstack { variant *begin; variant *top; - int size; + unsigned int size; } opstack; variant opstack_pop(opstack ** stackp) @@ -53,10 +53,16 @@ void opstack_push(opstack ** stackp, variant data) stack->top = stack->begin; *stackp = stack; } - if (stack->top - stack->begin == stack->size) { + if (stack->top == stack->begin + stack->size) { size_t pos = stack->top - stack->begin; + void *tmp; stack->size += stack->size; - stack->begin = realloc(stack->begin, sizeof(variant) * stack->size); + tmp = realloc(stack->begin, sizeof(variant) * stack->size); + if (!tmp) { + log_error("realloc out of memory"); + abort(); + } + stack->begin = (variant *)tmp; stack->top = stack->begin + pos; } *stack->top++ = data; @@ -66,7 +72,7 @@ void opstack_push(opstack ** stackp, variant data) ** static buffer malloc **/ -#define BBUFSIZE 128*1024 +#define BBUFSIZE 0x20000 static struct { char *begin; char *end; @@ -79,7 +85,7 @@ char *balloc(size_t size) static int init = 0; /* STATIC_XCALL: used across calls */ if (!init) { init = 1; - buffer.current = buffer.begin = malloc(BBUFSIZE); + buffer.current = buffer.begin = malloc(BBUFSIZE * sizeof(char)); buffer.end = buffer.begin + BBUFSIZE; } if (buffer.current + size > buffer.end) { @@ -269,7 +275,7 @@ static const char *parse_string(opstack ** stack, const char *in, } else { int ch = (unsigned char)(*ic); - int bytes; + size_t bytes; switch (ch) { case '\\': @@ -285,8 +291,8 @@ static const char *parse_string(opstack ** stack, const char *in, if (ic == NULL) return NULL; c = (char *)opop_v(stack); - bytes = (int)(c ? strlcpy(oc, c, size) : 0); - if (bytes < (int)size) + bytes = (c ? strlcpy(oc, c, size) : 0); + if (bytes < size) oc += bytes; else oc += size; @@ -363,7 +369,7 @@ static const char *parse(opstack ** stack, const char *inn, const char *translate(const char *format, const void *userdata, const char *vars, variant args[]) { - int i = 0; + unsigned int i = 0; const char *ic = vars; char symbol[32]; char *oc = symbol; diff --git a/storage b/storage index 2bcd3b1e6..86b967441 160000 --- a/storage +++ b/storage @@ -1 +1 @@ -Subproject commit 2bcd3b1e64764321773672333bd133a61b35b840 +Subproject commit 86b96744157eb08c55998df4c12fa2e073005b49 diff --git a/tests/data/184.dat b/tests/data/184.dat index 74d72258a..ffe368510 100644 Binary files a/tests/data/184.dat and b/tests/data/184.dat differ diff --git a/tests/write-reports.sh b/tests/write-reports.sh new file mode 100755 index 000000000..775fdb622 --- /dev/null +++ b/tests/write-reports.sh @@ -0,0 +1,40 @@ +cleanup () { +rm -rf reports score +} + +setup() { +ln -sf ../scripts/config.lua +} + +quit() { +test -n "$2" && echo $2 +exit $1 +} + +ROOT=`pwd` +while [ ! -d $ROOT/.git ]; do + ROOT=`dirname $ROOT` +done + +set -e +cd $ROOT/tests +setup +cleanup +VALGRIND=`which valgrind` +SERVER=../Debug/eressea/eressea +if [ -n "$VALGRIND" ]; then +SUPP=../share/ubuntu-12_04.supp +SERVER="$VALGRIND --suppressions=$SUPP --error-exitcode=1 --leak-check=no $SERVER" +fi +echo "running $SERVER" +$SERVER -t 184 ../scripts/reports.lua +[ -d reports ] || quit 4 "no reports directory created" +CRFILE=184-zvto.cr +grep -q PARTEI reports/$CRFILE || quit 1 "CR did not contain any factions" +grep -q REGION reports/$CRFILE || quit 2 "CR did not contain any regions" +grep -q SCHIFF reports/$CRFILE || quit 3 "CR did not contain any ships" +grep -q BURG reports/$CRFILE || quit 4 "CR did not contain any buildings" +grep -q EINHEIT reports/$CRFILE || quit 5 "CR did not contain any units" +grep -q GEGENSTAENDE reports/$CRFILE || quit 6 "CR did not contain any items" +echo "integration tests: PASS" +cleanup