diff --git a/res/core/de/strings.xml b/res/core/de/strings.xml index cd5dac609..d5b6dba34 100644 --- a/res/core/de/strings.xml +++ b/res/core/de/strings.xml @@ -5854,11 +5854,10 @@ das 50fache und auch im Kampf werden sich die erhöhte Kraft und die trollisch zähe Haut positiv auswirken. - This artifact gives the one wearing it + This artifact gives the wearer the strength of a cavetroll. He will be able to - carry fifty times as much as normal and also in - combat his enhanced strength and tough troll - skin will serve him well. + carry fifty times his normal load, as well as + gain strength and tough troll skin in combat. Der Schwarzmagier kann mit diesem @@ -5868,7 +5867,7 @@ Region werden einen Großteil ihrer Aura verlieren. With this dark ritual the - chaossorcerer causes a deep rift to appear in + chaos sorcerer causes a deep rift to appear in the astral balance that will tear all magical power from a region. All spellcasters in that region will lose most of their aura. diff --git a/scripts/tests/e2/ships.lua b/scripts/tests/e2/ships.lua new file mode 100644 index 000000000..151afc12c --- /dev/null +++ b/scripts/tests/e2/ships.lua @@ -0,0 +1,42 @@ +require "lunit" + +module("tests.e2.ships", package.seeall, lunit.testcase) + +function setup() + eressea.settings.set("rules.ship.damage.nocrewocean", "0") + eressea.settings.set("rules.ship.damage.nocrew", "0") + eressea.settings.set("rules.ship.drifting", "0") +end + +function test_ship_requires_skill() + local r1 = region.create(0, 0, "ocean") + local r2 = region.create(1, 0, "ocean") + local f = faction.create("fake@eressea.de", "human", "de") + local u1 = unit.create(f, r1, 1) + u1.name = "fake" + u1.ship = ship.create(r1, "longboat") + u1:clear_orders() + u1:add_order("NACH O") + process_orders() + assert_equal(r1, u1.ship.region) + assert_equal(r1, u1.region) +end + +function no_test_ship_happy_case() + local r1 = region.create(0, 0, "ocean") + local r2 = region.create(1, 0, "ocean") + local f = faction.create("hodor@eressea.de", "human", "de") + local u1 = unit.create(f, r1, 1) + local u2 = unit.create(f, r1, 1) + u1.ship = ship.create(r1, "longboat") + u2.ship = u1.ship + u1:clear_orders() + u1:add_order("NACH O") + u1:set_skill("sailing", 1) -- cptskill = 1 + u2:set_skill("sailing", 9) -- sumskill = 10 + process_orders() + assert_equal(r2, u1.ship.region) + assert_equal(r2, u1.region) + assert_equal(r2, u2.region) +end + diff --git a/src/kernel/jsonconf.c b/src/kernel/jsonconf.c index 8db61376c..f9a025432 100644 --- a/src/kernel/jsonconf.c +++ b/src/kernel/jsonconf.c @@ -323,6 +323,9 @@ static void json_ship(cJSON *json, ship_type *st) { if (strcmp(child->string, "range") == 0) { st->range = child->valueint; } + else if (strcmp(child->string, "maxrange") == 0) { + st->range_max = child->valueint; + } else { log_error("ship %s contains unknown attribute %s", json->string, child->string); } diff --git a/src/kernel/jsonconf.test.c b/src/kernel/jsonconf.test.c index bbe222fb6..50cba3891 100644 --- a/src/kernel/jsonconf.test.c +++ b/src/kernel/jsonconf.test.c @@ -155,7 +155,9 @@ static void test_ships(CuTest * tc) { const char * data = "{\"ships\": { \"boat\" : { " "\"construction\" : { \"maxsize\" : 20, \"reqsize\" : 10, \"minskill\" : 1 }," - "\"coasts\" : [ \"plain\" ]" + "\"coasts\" : [ \"plain\" ]," + "\"range\" : 8," + "\"maxrange\" : 16" "}}}"; cJSON *json = cJSON_Parse(data); @@ -175,6 +177,8 @@ static void test_ships(CuTest * tc) CuAssertIntEquals(tc, 10, st->construction->reqsize); CuAssertIntEquals(tc, 20, st->construction->maxsize); CuAssertIntEquals(tc, 1, st->construction->minskill); + CuAssertIntEquals(tc, 8, st->range); + CuAssertIntEquals(tc, 16, st->range_max); ter = get_terrain("plain"); CuAssertPtrNotNull(tc, ter); diff --git a/src/kernel/ship.c b/src/kernel/ship.c index dfb840179..dc1e228af 100644 --- a/src/kernel/ship.c +++ b/src/kernel/ship.c @@ -270,11 +270,7 @@ const char *write_shipname(const ship * sh, char *ibuf, size_t size) static int ShipSpeedBonus(const unit * u) { - static int level = -1; - if (level == -1) { - level = - get_param_int(global.parameters, "movement.shipspeed.skillbonus", 0); - } + int level = get_param_int(global.parameters, "movement.shipspeed.skillbonus", 0); if (level > 0) { ship *sh = u->ship; int skl = effskill(u, SK_SAILING); @@ -305,6 +301,7 @@ int shipspeed(const ship * sh, const unit * u) static bool init; attrib *a; struct curse *c; + int bonus; assert(sh); if (!u) u = ship_owner(sh); @@ -334,7 +331,19 @@ int shipspeed(const ship * sh, const unit * u) } } - k += ShipSpeedBonus(u); + bonus = ShipSpeedBonus(u); + if (bonus > 0 && sh->type->range_max>sh->type->range) { + int crew = crew_skill(sh); + int crew_bonus = (crew / sh->type->sumskill / 2) - 1; + if (crew_bonus > 0) { + bonus = _min(bonus, crew_bonus); + bonus = _min(bonus, sh->type->range_max - sh->type->range); + } + else { + bonus = 0; + } + } + k += bonus; a = a_find(sh->attribs, &at_speedup); while (a != NULL && a->type == &at_speedup) { @@ -348,10 +357,6 @@ int shipspeed(const ship * sh, const unit * u) c = c->nexthash; } -#ifdef SHIPSPEED - k *= SHIPSPEED; -#endif - if (sh->damage>0) { int size = sh->size * DAMAGE_SCALE; k *= (size - sh->damage); diff --git a/src/kernel/ship.h b/src/kernel/ship.h index c8fb5dfe7..6ba2de75d 100644 --- a/src/kernel/ship.h +++ b/src/kernel/ship.h @@ -36,6 +36,7 @@ extern "C" { char *_name; int range; /* range in regions */ + int range_max; int flags; /* flags */ int combat; /* modifier for combat */ int fishing; /* weekly income from fishing */ @@ -125,7 +126,6 @@ extern "C" { void ship_setname(struct ship *self, const char *name); int shipspeed(const struct ship *sh, const struct unit *u); int crew_skill(const struct ship *sh); - #ifdef __cplusplus } #endif diff --git a/src/kernel/ship.test.c b/src/kernel/ship.test.c index 05a735d26..380274a45 100644 --- a/src/kernel/ship.test.c +++ b/src/kernel/ship.test.c @@ -398,18 +398,16 @@ static void test_crew_skill(CuTest *tc) { static ship *setup_ship(void) { region *r; ship_type *stype; - ship *sh; - test_cleanup(); - test_create_world(); + set_param(&global.parameters, "movement.shipspeed.skillbonus", "0"); r = test_create_region(0, 0, test_create_terrain("ocean", 0)); stype = test_create_shiptype("longboat"); stype->cptskill = 1; stype->sumskill = 10; stype->minskill = 1; stype->range = 2; - sh = test_create_ship(r, stype); - return sh; + stype->range_max = 4; + return test_create_ship(r, stype); } static void setup_crew(ship *sh, struct faction *f, unit **cap, unit **crew) { @@ -522,27 +520,20 @@ static void test_shipspeed_damage(CuTest *tc) { static void test_shipspeed(CuTest *tc) { ship *sh; - ship_type *stype; + const ship_type *stype; region *r; - struct faction *f; unit *cap, *crew; test_cleanup(); sh = setup_ship(); r = sh->region; - f = test_create_faction(0); - assert(r && f); - stype = st_get_or_create(sh->type->_name); + stype = sh->type; CuAssertIntEquals_Msg(tc, "ship without a captain cannot move", 0, shipspeed(sh, NULL)); - cap = test_create_unit(f, r); - crew = test_create_unit(f, r); - cap->ship = sh; - crew->ship = sh; + setup_crew(sh, 0, &cap, &crew); + CuAssertPtrEquals(tc, cap, ship_owner(sh)); - set_level(cap, SK_SAILING, stype->cptskill); - set_level(crew, SK_SAILING, stype->sumskill - stype->cptskill); CuAssertIntEquals_Msg(tc, "ship with fully skilled crew can sail at max speed", 2, shipspeed(sh, cap)); CuAssertIntEquals_Msg(tc, "shipspeed without a hint defaults to captain", 2, shipspeed(sh, NULL)); @@ -557,7 +548,36 @@ static void test_shipspeed(CuTest *tc) { set_level(crew, SK_SAILING, (stype->sumskill - stype->cptskill) * 11); set_level(cap, SK_SAILING, stype->cptskill + 10); CuAssertIntEquals_Msg(tc, "regular skills should not exceed sh.range", 2, shipspeed(sh, cap)); +} +static void test_shipspeed_max_range(CuTest *tc) { + ship *sh; + ship_type *stype; + region *r; + struct faction *f; + unit *cap, *crew; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + set_param(&global.parameters, "movement.shipspeed.skillbonus", "5"); + r = sh->region; + f = test_create_faction(0); + assert(r && f); + stype = st_get_or_create(sh->type->_name); + + set_level(cap, SK_SAILING, stype->cptskill + 4); + set_level(crew, SK_SAILING, (stype->sumskill - stype->cptskill) * 4); + CuAssertIntEquals_Msg(tc, "skill bonus requires at least movement.shipspeed.skillbonus points", 2, shipspeed(sh, cap)); + + set_level(cap, SK_SAILING, stype->cptskill + 5); + set_level(crew, SK_SAILING, (stype->sumskill - stype->cptskill) * 5); + CuAssertIntEquals_Msg(tc, "skill bonus from movement.shipspeed.skillbonus", 3, shipspeed(sh, cap)); + + set_level(cap, SK_SAILING, stype->cptskill + 15); + set_level(crew, SK_SAILING, (stype->sumskill - stype->cptskill) * 15); + CuAssertIntEquals_Msg(tc, "skill-bonus cannot exceed max_range", 4, shipspeed(sh, cap)); + test_cleanup(); } CuSuite *get_ship_suite(void) @@ -574,6 +594,7 @@ CuSuite *get_ship_suite(void) SUITE_ADD_TEST(suite, test_shipowner_goes_to_other_after_leave); SUITE_ADD_TEST(suite, test_shipowner_goes_to_same_faction_after_leave); SUITE_ADD_TEST(suite, test_shipowner_goes_to_empty_unit_after_leave); + SUITE_ADD_TEST(suite, test_crew_skill); SUITE_ADD_TEST(suite, test_shipspeed); SUITE_ADD_TEST(suite, test_shipspeed_stormwind); SUITE_ADD_TEST(suite, test_shipspeed_nodrift); @@ -581,5 +602,6 @@ CuSuite *get_ship_suite(void) SUITE_ADD_TEST(suite, test_shipspeed_at_speedup); SUITE_ADD_TEST(suite, test_shipspeed_race_bonus); SUITE_ADD_TEST(suite, test_shipspeed_damage); + SUITE_ADD_TEST(suite, test_shipspeed_max_range); return suite; } diff --git a/src/kernel/unit.c b/src/kernel/unit.c index 310a407bd..b2601e75e 100644 --- a/src/kernel/unit.c +++ b/src/kernel/unit.c @@ -1807,7 +1807,7 @@ void scale_number(unit * u, int n) const struct race *u_irace(const struct unit *u) { - if (u->irace && skill_enabled(SK_STEALTH)) { + if (u->irace) { return u->irace; } return u->_race; diff --git a/src/kernel/xmlreader.c b/src/kernel/xmlreader.c index fca49e8ff..da1fed792 100644 --- a/src/kernel/xmlreader.c +++ b/src/kernel/xmlreader.c @@ -509,6 +509,7 @@ static int parse_ships(xmlDocPtr doc) st->minskill = xml_ivalue(node, "minskill", st->minskill); st->sumskill = xml_ivalue(node, "sumskill", st->sumskill); st->range = xml_ivalue(node, "range", st->range); + st->range_max = xml_ivalue(node, "maxrange", st->range_max); st->storm = xml_fvalue(node, "storm", st->storm); /* reading eressea/ships/ship/construction */ diff --git a/src/move.c b/src/move.c index 7d6f69371..4fd9e6dab 100644 --- a/src/move.c +++ b/src/move.c @@ -469,9 +469,9 @@ static bool cansail(const region * r, ship * sh) return true; } -int enoughsailors(const ship * sh, int sumskill) +int enoughsailors(const ship * sh, int crew_skill) { - return sumskill >= sh->type->sumskill; + return crew_skill >= sh->type->sumskill; } /* ------------------------------------------------------------- */ diff --git a/src/spells.c b/src/spells.c index e811967bd..8b504487a 100644 --- a/src/spells.c +++ b/src/spells.c @@ -4582,7 +4582,7 @@ int sp_illusionary_shapeshift(castorder * co) irace = u_irace(u); if (irace == u_race(u)) { trigger *trestore = trigger_changerace(u, NULL, irace); - add_trigger(&u->attribs, "timer", trigger_timeout((int)power + 2, + add_trigger(&u->attribs, "timer", trigger_timeout((int)power + 3, trestore)); u->irace = rc; } diff --git a/src/spells.test.c b/src/spells.test.c index 5ecb8bd88..5df2f1ca7 100644 --- a/src/spells.test.c +++ b/src/spells.test.c @@ -42,26 +42,29 @@ static void test_dreams(CuTest *tc) { u2 = test_create_unit(f2, r); test_create_castorder(&order, u1, 10, 10., 0); + level = sp_gooddreams(&order); CuAssertIntEquals(tc, 10, level); - curse *curse = get_curse(r->attribs, ct_find("gbdream")); CuAssertTrue(tc, curse && curse->duration > 1); CuAssertTrue(tc, curse->effect == 1); a_age(&r->attribs); + CuAssertIntEquals_Msg(tc, "good dreams give +1 to allies", 1, get_modifier(u1, SK_MELEE, 11, r, false)); + CuAssertIntEquals_Msg(tc, "good dreams have no effect on non-allies", 0, get_modifier(u2, SK_MELEE, 11, r, false)); - 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, 10, 10., 0); level = sp_baddreams(&order); CuAssertIntEquals(tc, 10, level); a_age(&r->attribs); + CuAssertIntEquals_Msg(tc, "bad dreams have no effect on allies", 0, get_modifier(u1, SK_MELEE, 11, r, false)); + CuAssertIntEquals_Msg(tc, "bad dreams give -1 to non-allies", -1, get_modifier(u2, SK_MELEE, 11, r, false)); - CuAssertIntEquals(tc, 1, get_modifier(u1, SK_MELEE, 11, r, false)); - CuAssertIntEquals(tc, -1, get_modifier(u2, SK_MELEE, 11, r, false)); + sp_gooddreams(&order); + sp_baddreams(&order); + a_age(&r->attribs); + CuAssertIntEquals_Msg(tc, "good dreams in same region as bad dreams", 1, get_modifier(u1, SK_MELEE, 11, r, false)); + CuAssertIntEquals_Msg(tc, "bad dreams in same region as good dreams", -1, get_modifier(u2, SK_MELEE, 11, r, false)); free_castorder(&order); test_cleanup(); diff --git a/src/spy.c b/src/spy.c index d993a9ab7..8264fcabf 100644 --- a/src/spy.c +++ b/src/spy.c @@ -214,7 +214,6 @@ int setstealth_cmd(unit * u, struct order *ord) char token[64]; const char *s; int level, rule; - const race *trace; init_order(ord); s = gettoken(token, sizeof(token)); @@ -237,47 +236,51 @@ int setstealth_cmd(unit * u, struct order *ord) return 0; } - trace = findrace(s, u->faction->locale); - if (trace) { - /* demons can cloak as other player-races */ - if (u_race(u) == get_race(RC_DAEMON)) { - race_t allowed[] = { RC_DWARF, RC_ELF, RC_ORC, RC_GOBLIN, RC_HUMAN, - RC_TROLL, RC_DAEMON, RC_INSECT, RC_HALFLING, RC_CAT, RC_AQUARIAN, - NORACE - }; - int i; - for (i = 0; allowed[i] != NORACE; ++i) - if (get_race(allowed[i]) == trace) - break; - if (get_race(allowed[i]) == trace) { - u->irace = trace; - if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) - set_racename(&u->attribs, NULL); + if (skill_enabled(SK_STEALTH)) { /* hack! E3 erlaubt keine Tarnung */ + const race *trace; + + trace = findrace(s, u->faction->locale); + if (trace) { + /* demons can cloak as other player-races */ + if (u_race(u) == get_race(RC_DAEMON)) { + race_t allowed[] = { RC_DWARF, RC_ELF, RC_ORC, RC_GOBLIN, RC_HUMAN, + RC_TROLL, RC_DAEMON, RC_INSECT, RC_HALFLING, RC_CAT, RC_AQUARIAN, + NORACE + }; + int i; + for (i = 0; allowed[i] != NORACE; ++i) + if (get_race(allowed[i]) == trace) + break; + if (get_race(allowed[i]) == trace) { + u->irace = trace; + if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) + set_racename(&u->attribs, NULL); + } + return 0; + } + + /* Singdrachen koennen sich nur als Drachen tarnen */ + if (u_race(u) == get_race(RC_SONGDRAGON) + || u_race(u) == get_race(RC_BIRTHDAYDRAGON)) { + if (trace == get_race(RC_SONGDRAGON) || trace == get_race(RC_FIREDRAGON) + || trace == get_race(RC_DRAGON) || trace == get_race(RC_WYRM)) { + u->irace = trace; + if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) + set_racename(&u->attribs, NULL); + } + return 0; + } + + /* Daemomen und Illusionsparteien koennen sich als andere race tarnen */ + if (u_race(u)->flags & RCF_SHAPESHIFT) { + if (playerrace(trace)) { + u->irace = trace; + if ((u_race(u)->flags & RCF_SHAPESHIFTANY) && get_racename(u->attribs)) + set_racename(&u->attribs, NULL); + } } return 0; } - - /* Singdrachen koennen sich nur als Drachen tarnen */ - if (u_race(u) == get_race(RC_SONGDRAGON) - || u_race(u) == get_race(RC_BIRTHDAYDRAGON)) { - if (trace == get_race(RC_SONGDRAGON) || trace == get_race(RC_FIREDRAGON) - || trace == get_race(RC_DRAGON) || trace == get_race(RC_WYRM)) { - u->irace = trace; - if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) - set_racename(&u->attribs, NULL); - } - return 0; - } - - /* Daemomen und Illusionsparteien koennen sich als andere race tarnen */ - if (u_race(u)->flags & RCF_SHAPESHIFT) { - if (playerrace(trace)) { - u->irace = trace; - if ((u_race(u)->flags & RCF_SHAPESHIFTANY) && get_racename(u->attribs)) - set_racename(&u->attribs, NULL); - } - } - return 0; } switch (findparam(s, u->faction->locale)) {