diff --git a/.gitignore b/.gitignore index 66d38f2a9..1c6507e5d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ Thumbs.db .gdb_history *.cfg *.cmd +tmp/ diff --git a/src/kernel/ship.c b/src/kernel/ship.c index a01326bdd..dfb840179 100644 --- a/src/kernel/ship.c +++ b/src/kernel/ship.c @@ -191,13 +191,15 @@ ship *new_ship(const ship_type * stype, region * r, const struct locale *lang) sh->type = stype; sh->region = r; - sname = LOC(lang, stype->_name); - if (!sname) { - sname = LOC(lang, parameters[P_SHIP]); + if (lang) { + sname = LOC(lang, stype->_name); if (!sname) { - sname = parameters[P_SHIP]; + sname = LOC(lang, parameters[P_SHIP]); } } + if (!sname) { + sname = parameters[P_SHIP]; + } assert(sname); slprintf(buffer, sizeof(buffer), "%s %s", sname, shipid(sh)); sh->name = _strdup(buffer); @@ -282,22 +284,41 @@ static int ShipSpeedBonus(const unit * u) return 0; } +int crew_skill(const ship *sh) { + int n = 0; + unit *u; + + n = 0; + + for (u = sh->region->units; u; u = u->next) { + if (u->ship == sh) { + n += eff_skill(u, SK_SAILING, sh->region) * u->number; + } + } + return n; +} + int shipspeed(const ship * sh, const unit * u) { - double k = sh->type->range; + int k = sh->type->range; static const struct curse_type *stormwind_ct, *nodrift_ct; static bool init; attrib *a; struct curse *c; + assert(sh); + if (!u) u = ship_owner(sh); + if (!u) return 0; + assert(u->ship == sh); + assert(u == ship_owner(sh)); + assert(sh->type->construction); + assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + if (!init) { init = true; stormwind_ct = ct_find("stormwind"); nodrift_ct = ct_find("nodrift"); } - - assert(u->ship == sh); - assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ if (sh->size != sh->type->construction->maxsize) return 0; @@ -323,7 +344,7 @@ int shipspeed(const ship * sh, const unit * u) c = get_curse(sh->attribs, ct_find("shipspeedup")); while (c) { - k += curse_geteffect(c); + k += curse_geteffect_int(c); c = c->nexthash; } @@ -331,12 +352,12 @@ int shipspeed(const ship * sh, const unit * u) k *= SHIPSPEED; #endif - if (sh->damage) - k = - (k * (sh->size * DAMAGE_SCALE - sh->damage) + sh->size * DAMAGE_SCALE - - 1) / (sh->size * DAMAGE_SCALE); - - return (int)k; + if (sh->damage>0) { + int size = sh->size * DAMAGE_SCALE; + k *= (size - sh->damage); + k = (k + size - 1) / size; + } + return k; } const char *shipname(const ship * sh) @@ -442,18 +463,3 @@ const char *ship_getname(const ship * self) { return self->name; } - -unit *get_captain(const ship * sh) -{ - const region *r = sh->region; - unit *u; - - for (u = r->units; u; u = u->next) { - if (u->ship == sh && u->number - && eff_skill(u, SK_SAILING, r) >= sh->type->cptskill) - return u; - } - - return NULL; -} - diff --git a/src/kernel/ship.h b/src/kernel/ship.h index f123242df..c8fb5dfe7 100644 --- a/src/kernel/ship.h +++ b/src/kernel/ship.h @@ -124,7 +124,7 @@ extern "C" { const char *ship_getname(const struct ship *self); void ship_setname(struct ship *self, const char *name); int shipspeed(const struct ship *sh, const struct unit *u); - struct unit *get_captain(const struct ship *sh); + int crew_skill(const struct ship *sh); #ifdef __cplusplus } diff --git a/src/kernel/ship.test.c b/src/kernel/ship.test.c index 6d0c1b0cb..05a735d26 100644 --- a/src/kernel/ship.test.c +++ b/src/kernel/ship.test.c @@ -5,11 +5,18 @@ #include #include #include +#include + +#include + +#include +#include #include #include #include #include +#include static void test_register_ship(CuTest * tc) { @@ -367,6 +374,192 @@ static void test_stype_defaults(CuTest *tc) { test_cleanup(); } +static void test_crew_skill(CuTest *tc) { + ship *sh; + region *r; + struct faction *f; + int i; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + assert(r && f); + sh = test_create_ship(r, st_find("boat")); + for (i = 0; i != 4; ++i) { + unit * u = test_create_unit(f, r); + set_level(u, SK_SAILING, 5); + u->ship = sh; + } + CuAssertIntEquals(tc, 20, crew_skill(sh)); + test_cleanup(); +} + +static ship *setup_ship(void) { + region *r; + ship_type *stype; + ship *sh; + + test_cleanup(); + test_create_world(); + 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; +} + +static void setup_crew(ship *sh, struct faction *f, unit **cap, unit **crew) { + if (!f) f = test_create_faction(0); + assert(cap); + assert(crew); + *cap = test_create_unit(f, sh->region); + *crew = test_create_unit(f, sh->region); + (*cap)->ship = sh; + (*crew)->ship = sh; + set_level(*cap, SK_SAILING, sh->type->cptskill); + set_level(*crew, SK_SAILING, sh->type->sumskill - sh->type->cptskill); +} + +static void test_shipspeed_stormwind(CuTest *tc) { + ship *sh; + unit *cap, *crew; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + register_shipcurse(); + assert(sh && cap && crew); + + create_curse(0, &sh->attribs, ct_find("stormwind"), 1, 1, 1, 0); + CuAssertIntEquals_Msg(tc, "stormwind doubles ship range", sh->type->range * 2, shipspeed(sh, cap)); + test_cleanup(); +} + +static void test_shipspeed_nodrift(CuTest *tc) { + ship *sh; + unit *cap, *crew; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + register_shipcurse(); + assert(sh && cap && crew); + + create_curse(0, &sh->attribs, ct_find("nodrift"), 1, 1, 1, 0); + CuAssertIntEquals_Msg(tc, "nodrift adds +1 to range", sh->type->range + 1, shipspeed(sh, cap)); + test_cleanup(); +} + +static void test_shipspeed_shipspeedup(CuTest *tc) { + ship *sh; + unit *cap, *crew; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + register_shipcurse(); + assert(sh && cap && crew); + + create_curse(0, &sh->attribs, ct_find("shipspeedup"), 1, 1, 3, 0); + CuAssertIntEquals_Msg(tc, "shipspeedup adds effect to range", sh->type->range + 3, shipspeed(sh, cap)); + test_cleanup(); +} + +static void test_shipspeed_at_speedup(CuTest *tc) { + ship *sh; + unit *cap, *crew; + attrib *a; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + assert(sh && cap && crew); + + a = a_new(&at_speedup); + a->data.i = 3; + a_add(&sh->attribs, a); + CuAssertIntEquals_Msg(tc, "at_speedup adds value to range", sh->type->range + 3, shipspeed(sh, cap)); + test_cleanup(); +} + +static void test_shipspeed_race_bonus(CuTest *tc) { + ship *sh; + unit *cap, *crew; + race *rc; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + assert(sh && cap && crew); + + rc = rc_get_or_create(cap->_race->_name); + rc->flags |= RCF_SHIPSPEED; + CuAssertIntEquals_Msg(tc, "captain with RCF_SHIPSPEED adds +1 to range", sh->type->range + 1, shipspeed(sh, cap)); + test_cleanup(); +} + +static void test_shipspeed_damage(CuTest *tc) { + ship *sh; + unit *cap, *crew; + + test_cleanup(); + sh = setup_ship(); + setup_crew(sh, 0, &cap, &crew); + assert(sh && cap && crew); + + sh->damage = 1; + CuAssertIntEquals_Msg(tc, "minimally damaged ships lose no range", 2, shipspeed(sh, cap)); + sh->damage = sh->size * DAMAGE_SCALE / 2; + CuAssertIntEquals_Msg(tc, "damaged ships lose range", 1, shipspeed(sh, cap)); + sh->damage = sh->size * DAMAGE_SCALE; + CuAssertIntEquals_Msg(tc, "fully damaged ships have no range", 0, shipspeed(sh, cap)); + test_cleanup(); +} + +static void test_shipspeed(CuTest *tc) { + ship *sh; + 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); + + 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; + 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)); + + set_level(cap, SK_SAILING, stype->cptskill + 5); + set_level(crew, SK_SAILING, (stype->sumskill - stype->cptskill) * 10); + CuAssertIntEquals_Msg(tc, "higher skills should not affect top speed", 2, shipspeed(sh, cap)); + set_level(cap, SK_SAILING, stype->cptskill); + set_level(crew, SK_SAILING, stype->sumskill - stype->cptskill); + + CuAssertIntEquals(tc, 2, shipspeed(sh, cap)); + + 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)); + +} + CuSuite *get_ship_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -381,5 +574,12 @@ 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_shipspeed); + SUITE_ADD_TEST(suite, test_shipspeed_stormwind); + SUITE_ADD_TEST(suite, test_shipspeed_nodrift); + SUITE_ADD_TEST(suite, test_shipspeed_shipspeedup); + SUITE_ADD_TEST(suite, test_shipspeed_at_speedup); + SUITE_ADD_TEST(suite, test_shipspeed_race_bonus); + SUITE_ADD_TEST(suite, test_shipspeed_damage); return suite; } diff --git a/src/laws.c b/src/laws.c index af1347bd1..b60e15ac9 100755 --- a/src/laws.c +++ b/src/laws.c @@ -2797,17 +2797,17 @@ void sinkships(struct region * r) ship *sh = *shp; if (!sh->type->construction || sh->size >= sh->type->construction->maxsize) { - if (fval(r->terrain, SEA_REGION) && (!enoughsailors(sh, r) - || get_captain(sh) == NULL)) { - /* Schiff nicht seetüchtig */ - float dmg = get_param_flt(global.parameters, - "rules.ship.damage.nocrewocean", - 0.30F); - damage_ship(sh, dmg); - } - if (ship_owner(sh) == NULL) { - float dmg = get_param_flt(global.parameters, "rules.ship.damage.nocrew", - 0.05F); + if (fval(r->terrain, SEA_REGION)) { + if (!enoughsailors(sh, crew_skill(sh))) { + // ship is at sea, but not enough people to control it + float dmg = get_param_flt(global.parameters, + "rules.ship.damage.nocrewocean", + 0.30F); + damage_ship(sh, dmg); + } + } else if (!ship_owner(sh)) { + // any ship lying around without an owner slowly rots + float dmg = get_param_flt(global.parameters, "rules.ship.damage.nocrew", 0.05F); damage_ship(sh, dmg); } } diff --git a/src/move.c b/src/move.c index a9ed36b3e..7d6f69371 100644 --- a/src/move.c +++ b/src/move.c @@ -469,18 +469,9 @@ static bool cansail(const region * r, ship * sh) return true; } -int enoughsailors(const ship * sh, const region * r) +int enoughsailors(const ship * sh, int sumskill) { - int n; - unit *u; - - n = 0; - - for (u = r->units; u; u = u->next) { - if (u->ship == sh) - n += eff_skill(u, SK_SAILING, r) * u->number; - } - return n >= sh->type->sumskill; + return sumskill >= sh->type->sumskill; } /* ------------------------------------------------------------- */ @@ -778,7 +769,7 @@ static void drifting_ships(region * r) assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ if (captain && sh->size == sh->type->construction->maxsize - && enoughsailors(sh, r) && cansail(r, sh)) { + && enoughsailors(sh, crew_skill(sh)) && cansail(r, sh)) { shp = &sh->next; continue; } @@ -1767,7 +1758,7 @@ static bool ship_ready(const region * r, unit * u) cmistake(u, u->thisorder, 15, MSG_MOVE); return false; } - if (!enoughsailors(u->ship, r)) { + if (!enoughsailors(u->ship, crew_skill(u->ship))) { cmistake(u, u->thisorder, 1, MSG_MOVE); /* mistake(u, u->thisorder, "Auf dem Schiff befinden sich zuwenig erfahrene Seeleute.", MSG_MOVE); */ diff --git a/src/move.h b/src/move.h index f35012b07..7674c43d7 100644 --- a/src/move.h +++ b/src/move.h @@ -61,7 +61,7 @@ extern "C" { void run_to(struct unit *u, struct region *to); struct unit *is_guarded(struct region *r, struct unit *u, unsigned int mask); bool is_guard(const struct unit *u, unsigned int mask); - int enoughsailors(const struct ship *sh, const struct region *r); + int enoughsailors(const struct ship *sh, int sumskill); bool canswim(struct unit *u); bool canfly(struct unit *u); void travelthru(const struct unit *u, struct region *r); diff --git a/src/spy.c b/src/spy.c index a440ab3aa..d993a9ab7 100644 --- a/src/spy.c +++ b/src/spy.c @@ -342,7 +342,7 @@ int setstealth_cmd(unit * u, struct order *ord) return 0; } -static int crew_skill(region * r, faction * f, ship * sh, skill_t sk) +static int top_skill(region * r, faction * f, ship * sh, skill_t sk) { int value = 0; unit *u; @@ -517,7 +517,7 @@ int sabotage_cmd(unit * u, struct order *ord) r = u->region; if (u2->faction != u->faction) { skdiff = - eff_skill(u, SK_SPY, r) - crew_skill(r, u2->faction, sh, SK_PERCEPTION); + eff_skill(u, SK_SPY, r) - top_skill(r, u2->faction, sh, SK_PERCEPTION); } if (try_destruction(u, u2, sh, skdiff)) { sink_ship(r, sh, u); diff --git a/src/tests.c b/src/tests.c index 5a19ce9c0..4cb0d61c0 100644 --- a/src/tests.c +++ b/src/tests.c @@ -107,7 +107,19 @@ ship * test_create_ship(region * r, const ship_type * stype) ship_type * test_create_shiptype(const char * name) { ship_type * stype = st_get_or_create(name); - locale_setstring(default_locale, name, name); + stype->cptskill = 1; + stype->sumskill = 1; + stype->minskill = 1; + if (!stype->construction) { + stype->construction = calloc(1, sizeof(construction)); + stype->construction->maxsize = 5; + stype->construction->minskill = 1; + stype->construction->reqsize = 1; + stype->construction->skill = SK_SHIPBUILDING; + } + if (default_locale) { + locale_setstring(default_locale, name, name); + } return stype; }