Merge pull request #270 from ennorehling/feature/bug-2118-ship-max-range

feature 2118: high skills can give a higher maximum range
This commit is contained in:
Enno Rehling 2015-08-07 13:34:11 +02:00
commit 93f3a85d48
13 changed files with 165 additions and 83 deletions

View File

@ -5854,11 +5854,10 @@
das 50fache und auch im Kampf werden sich die das 50fache und auch im Kampf werden sich die
erhöhte Kraft und die trollisch zähe Haut erhöhte Kraft und die trollisch zähe Haut
positiv auswirken.</text> positiv auswirken.</text>
<text locale="en">This artifact gives the one wearing it <text locale="en">This artifact gives the wearer
the strength of a cavetroll. He will be able to the strength of a cavetroll. He will be able to
carry fifty times as much as normal and also in carry fifty times his normal load, as well as
combat his enhanced strength and tough troll gain strength and tough troll skin in combat.</text>
skin will serve him well.</text>
</string> </string>
<string name="auraleak"> <string name="auraleak">
<text locale="de">Der Schwarzmagier kann mit diesem <text locale="de">Der Schwarzmagier kann mit diesem

View File

@ -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

View File

@ -323,6 +323,9 @@ static void json_ship(cJSON *json, ship_type *st) {
if (strcmp(child->string, "range") == 0) { if (strcmp(child->string, "range") == 0) {
st->range = child->valueint; st->range = child->valueint;
} }
else if (strcmp(child->string, "maxrange") == 0) {
st->range_max = child->valueint;
}
else { else {
log_error("ship %s contains unknown attribute %s", json->string, child->string); log_error("ship %s contains unknown attribute %s", json->string, child->string);
} }

View File

@ -155,7 +155,9 @@ static void test_ships(CuTest * tc)
{ {
const char * data = "{\"ships\": { \"boat\" : { " const char * data = "{\"ships\": { \"boat\" : { "
"\"construction\" : { \"maxsize\" : 20, \"reqsize\" : 10, \"minskill\" : 1 }," "\"construction\" : { \"maxsize\" : 20, \"reqsize\" : 10, \"minskill\" : 1 },"
"\"coasts\" : [ \"plain\" ]" "\"coasts\" : [ \"plain\" ],"
"\"range\" : 8,"
"\"maxrange\" : 16"
"}}}"; "}}}";
cJSON *json = cJSON_Parse(data); cJSON *json = cJSON_Parse(data);
@ -175,6 +177,8 @@ static void test_ships(CuTest * tc)
CuAssertIntEquals(tc, 10, st->construction->reqsize); CuAssertIntEquals(tc, 10, st->construction->reqsize);
CuAssertIntEquals(tc, 20, st->construction->maxsize); CuAssertIntEquals(tc, 20, st->construction->maxsize);
CuAssertIntEquals(tc, 1, st->construction->minskill); CuAssertIntEquals(tc, 1, st->construction->minskill);
CuAssertIntEquals(tc, 8, st->range);
CuAssertIntEquals(tc, 16, st->range_max);
ter = get_terrain("plain"); ter = get_terrain("plain");
CuAssertPtrNotNull(tc, ter); CuAssertPtrNotNull(tc, ter);

View File

@ -270,11 +270,7 @@ const char *write_shipname(const ship * sh, char *ibuf, size_t size)
static int ShipSpeedBonus(const unit * u) static int ShipSpeedBonus(const unit * u)
{ {
static int level = -1; int level = get_param_int(global.parameters, "movement.shipspeed.skillbonus", 0);
if (level == -1) {
level =
get_param_int(global.parameters, "movement.shipspeed.skillbonus", 0);
}
if (level > 0) { if (level > 0) {
ship *sh = u->ship; ship *sh = u->ship;
int skl = effskill(u, SK_SAILING); int skl = effskill(u, SK_SAILING);
@ -305,6 +301,7 @@ int shipspeed(const ship * sh, const unit * u)
static bool init; static bool init;
attrib *a; attrib *a;
struct curse *c; struct curse *c;
int bonus;
assert(sh); assert(sh);
if (!u) u = ship_owner(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); a = a_find(sh->attribs, &at_speedup);
while (a != NULL && a->type == &at_speedup) { while (a != NULL && a->type == &at_speedup) {
@ -348,10 +357,6 @@ int shipspeed(const ship * sh, const unit * u)
c = c->nexthash; c = c->nexthash;
} }
#ifdef SHIPSPEED
k *= SHIPSPEED;
#endif
if (sh->damage>0) { if (sh->damage>0) {
int size = sh->size * DAMAGE_SCALE; int size = sh->size * DAMAGE_SCALE;
k *= (size - sh->damage); k *= (size - sh->damage);

View File

@ -36,6 +36,7 @@ extern "C" {
char *_name; char *_name;
int range; /* range in regions */ int range; /* range in regions */
int range_max;
int flags; /* flags */ int flags; /* flags */
int combat; /* modifier for combat */ int combat; /* modifier for combat */
int fishing; /* weekly income from fishing */ int fishing; /* weekly income from fishing */
@ -125,7 +126,6 @@ extern "C" {
void ship_setname(struct ship *self, const char *name); void ship_setname(struct ship *self, const char *name);
int shipspeed(const struct ship *sh, const struct unit *u); int shipspeed(const struct ship *sh, const struct unit *u);
int crew_skill(const struct ship *sh); int crew_skill(const struct ship *sh);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -398,18 +398,16 @@ static void test_crew_skill(CuTest *tc) {
static ship *setup_ship(void) { static ship *setup_ship(void) {
region *r; region *r;
ship_type *stype; ship_type *stype;
ship *sh;
test_cleanup(); set_param(&global.parameters, "movement.shipspeed.skillbonus", "0");
test_create_world();
r = test_create_region(0, 0, test_create_terrain("ocean", 0)); r = test_create_region(0, 0, test_create_terrain("ocean", 0));
stype = test_create_shiptype("longboat"); stype = test_create_shiptype("longboat");
stype->cptskill = 1; stype->cptskill = 1;
stype->sumskill = 10; stype->sumskill = 10;
stype->minskill = 1; stype->minskill = 1;
stype->range = 2; stype->range = 2;
sh = test_create_ship(r, stype); stype->range_max = 4;
return sh; return test_create_ship(r, stype);
} }
static void setup_crew(ship *sh, struct faction *f, unit **cap, unit **crew) { 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) { static void test_shipspeed(CuTest *tc) {
ship *sh; ship *sh;
ship_type *stype; const ship_type *stype;
region *r; region *r;
struct faction *f;
unit *cap, *crew; unit *cap, *crew;
test_cleanup(); test_cleanup();
sh = setup_ship(); sh = setup_ship();
r = sh->region; r = sh->region;
f = test_create_faction(0); stype = sh->type;
assert(r && f);
stype = st_get_or_create(sh->type->_name);
CuAssertIntEquals_Msg(tc, "ship without a captain cannot move", 0, shipspeed(sh, NULL)); CuAssertIntEquals_Msg(tc, "ship without a captain cannot move", 0, shipspeed(sh, NULL));
cap = test_create_unit(f, r); setup_crew(sh, 0, &cap, &crew);
crew = test_create_unit(f, r);
cap->ship = sh;
crew->ship = sh;
CuAssertPtrEquals(tc, cap, ship_owner(sh)); 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, "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)); 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(crew, SK_SAILING, (stype->sumskill - stype->cptskill) * 11);
set_level(cap, SK_SAILING, stype->cptskill + 10); set_level(cap, SK_SAILING, stype->cptskill + 10);
CuAssertIntEquals_Msg(tc, "regular skills should not exceed sh.range", 2, shipspeed(sh, cap)); 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) 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_other_after_leave);
SUITE_ADD_TEST(suite, test_shipowner_goes_to_same_faction_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_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);
SUITE_ADD_TEST(suite, test_shipspeed_stormwind); SUITE_ADD_TEST(suite, test_shipspeed_stormwind);
SUITE_ADD_TEST(suite, test_shipspeed_nodrift); 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_at_speedup);
SUITE_ADD_TEST(suite, test_shipspeed_race_bonus); SUITE_ADD_TEST(suite, test_shipspeed_race_bonus);
SUITE_ADD_TEST(suite, test_shipspeed_damage); SUITE_ADD_TEST(suite, test_shipspeed_damage);
SUITE_ADD_TEST(suite, test_shipspeed_max_range);
return suite; return suite;
} }

View File

@ -1807,7 +1807,7 @@ void scale_number(unit * u, int n)
const struct race *u_irace(const struct unit *u) const struct race *u_irace(const struct unit *u)
{ {
if (u->irace && skill_enabled(SK_STEALTH)) { if (u->irace) {
return u->irace; return u->irace;
} }
return u->_race; return u->_race;

View File

@ -509,6 +509,7 @@ static int parse_ships(xmlDocPtr doc)
st->minskill = xml_ivalue(node, "minskill", st->minskill); st->minskill = xml_ivalue(node, "minskill", st->minskill);
st->sumskill = xml_ivalue(node, "sumskill", st->sumskill); st->sumskill = xml_ivalue(node, "sumskill", st->sumskill);
st->range = xml_ivalue(node, "range", st->range); 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); st->storm = xml_fvalue(node, "storm", st->storm);
/* reading eressea/ships/ship/construction */ /* reading eressea/ships/ship/construction */

View File

@ -469,9 +469,9 @@ static bool cansail(const region * r, ship * sh)
return true; 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;
} }
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */

View File

@ -4582,7 +4582,7 @@ int sp_illusionary_shapeshift(castorder * co)
irace = u_irace(u); irace = u_irace(u);
if (irace == u_race(u)) { if (irace == u_race(u)) {
trigger *trestore = trigger_changerace(u, NULL, irace); 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)); trestore));
u->irace = rc; u->irace = rc;
} }

View File

@ -42,26 +42,29 @@ static void test_dreams(CuTest *tc) {
u2 = test_create_unit(f2, r); u2 = test_create_unit(f2, r);
test_create_castorder(&order, u1, 10, 10., 0); test_create_castorder(&order, u1, 10, 10., 0);
level = sp_gooddreams(&order); level = sp_gooddreams(&order);
CuAssertIntEquals(tc, 10, level); CuAssertIntEquals(tc, 10, level);
curse *curse = get_curse(r->attribs, ct_find("gbdream")); curse *curse = get_curse(r->attribs, ct_find("gbdream"));
CuAssertTrue(tc, curse && curse->duration > 1); CuAssertTrue(tc, curse && curse->duration > 1);
CuAssertTrue(tc, curse->effect == 1); CuAssertTrue(tc, curse->effect == 1);
a_age(&r->attribs); 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); level = sp_baddreams(&order);
CuAssertIntEquals(tc, 10, level); CuAssertIntEquals(tc, 10, level);
a_age(&r->attribs); 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)); sp_gooddreams(&order);
CuAssertIntEquals(tc, -1, get_modifier(u2, SK_MELEE, 11, r, false)); 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); free_castorder(&order);
test_cleanup(); test_cleanup();

View File

@ -214,7 +214,6 @@ int setstealth_cmd(unit * u, struct order *ord)
char token[64]; char token[64];
const char *s; const char *s;
int level, rule; int level, rule;
const race *trace;
init_order(ord); init_order(ord);
s = gettoken(token, sizeof(token)); s = gettoken(token, sizeof(token));
@ -237,6 +236,9 @@ int setstealth_cmd(unit * u, struct order *ord)
return 0; return 0;
} }
if (skill_enabled(SK_STEALTH)) { /* hack! E3 erlaubt keine Tarnung */
const race *trace;
trace = findrace(s, u->faction->locale); trace = findrace(s, u->faction->locale);
if (trace) { if (trace) {
/* demons can cloak as other player-races */ /* demons can cloak as other player-races */
@ -279,6 +281,7 @@ int setstealth_cmd(unit * u, struct order *ord)
} }
return 0; return 0;
} }
}
switch (findparam(s, u->faction->locale)) { switch (findparam(s, u->faction->locale)) {
case P_FACTION: case P_FACTION: