diff --git a/res/core/messages.xml b/res/core/messages.xml index be040f5db..8231edec3 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -1615,6 +1615,13 @@ "Die $ship($ship) wird bei einer Kollision mit einem Eisberg beschädigt." "The $ship($ship) has been damaged by a collision with an iceberg." + + + + + "Die $ship($ship) ist zu stark überladen und wird stark beschädigt." + "The $ship($ship) is massively overloaded and is damaged heavily." + diff --git a/src/bind_ship.c b/src/bind_ship.c index 595be35dd..22015f673 100644 --- a/src/bind_ship.c +++ b/src/bind_ship.c @@ -192,6 +192,20 @@ static int tolua_ship_get_type(lua_State * L) return 1; } +static int tolua_ship_get_damage(lua_State * L) +{ + ship *self = (ship *)tolua_tousertype(L, 1, 0); + lua_pushinteger(L, self->damage); + return 1; +} + +static int tolua_ship_set_damage(lua_State * L) +{ + ship *self = (ship *)tolua_tousertype(L, 1, 0); + self->damage = (int)tolua_tonumber(L, 2, 0); + return 0; +} + void tolua_ship_open(lua_State * L) { /* register user types */ @@ -217,6 +231,9 @@ void tolua_ship_open(lua_State * L) tolua_variable(L, TOLUA_CAST "coast", tolua_ship_get_coast, tolua_ship_set_coast); tolua_variable(L, TOLUA_CAST "type", tolua_ship_get_type, 0); + tolua_variable(L, TOLUA_CAST "damage", tolua_ship_get_damage, + tolua_ship_set_damage); + #ifdef TODO .property("weight", &ship_getweight) .property("capacity", &ship_getcapacity) diff --git a/src/kernel/ship.c b/src/kernel/ship.c index f20c8ef59..b10af5aeb 100644 --- a/src/kernel/ship.c +++ b/src/kernel/ship.c @@ -172,7 +172,7 @@ struct ship *findshipr(const region * r, int n) void damage_ship(ship * sh, double percent) { double damage = - DAMAGE_SCALE * sh->type->damage * percent * sh->size + sh->damage; + DAMAGE_SCALE * sh->type->damage * percent * sh->size + sh->damage + .000001; sh->damage = (int)damage; } @@ -469,3 +469,7 @@ const char *ship_getname(const ship * self) { return self->name; } + +int ship_damage_percent(const ship *ship) { + return (ship->damage * 100 + DAMAGE_SCALE - 1) / (ship->size * DAMAGE_SCALE); +} diff --git a/src/kernel/ship.h b/src/kernel/ship.h index 6ba2de75d..73b8a9718 100644 --- a/src/kernel/ship.h +++ b/src/kernel/ship.h @@ -126,6 +126,8 @@ 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); + + int ship_damage_percent(const struct ship *ship); #ifdef __cplusplus } #endif diff --git a/src/move.c b/src/move.c index f94fbfef1..848574f70 100644 --- a/src/move.c +++ b/src/move.c @@ -81,6 +81,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include +#include /* Bewegungsweiten: */ #define BP_WALKING 4 @@ -482,6 +483,27 @@ static bool cansail(const region * r, ship * sh) return true; } +static double overload(const region * r, ship * sh) +{ + /* sonst ist construction:: size nicht ship_type::maxsize */ + assert(!sh->type->construction + || sh->type->construction->improvement == NULL); + + if (sh->type->construction && sh->size != sh->type->construction->maxsize) { + return DBL_MAX; + } else { + int n = 0, p = 0; + int mcabins = sh->type->cabins; + + getshipweight(sh, &n, &p); + + double ovl = n / (double)sh->type->cargo; + if (mcabins) + ovl = _max(ovl, p / (double)mcabins); + return ovl; + } +} + int enoughsailors(const ship * sh, int crew_skill) { return crew_skill >= sh->type->sumskill; @@ -699,10 +721,69 @@ static void set_coast(ship * sh, region * r, region * rnext) } } +static double overload_start(void) { + return config_get_flt("rules.ship.overload.start", 1.1); +} + +static double overload_worse(void) { + return config_get_flt("rules.ship.overload.worse", 1.5); +} + +static double overload_worst(void) { + return config_get_flt("rules.ship.overload.worst", 5.0); +} + +static double overload_default_damage(void) { + return config_get_flt("rules.ship.overload.damage.default", 0.05); +} + +static double overload_max_damage(void) { + return config_get_flt("rules.ship.overload.damage.max", 0.37); +} + +double damage_overload(double overload) +{ + double damage, badness; + if (overload < overload_start()) + return 0; + damage = overload_default_damage(); + badness = overload - overload_worse(); + if (badness >= 0) { + assert(overload_worst() > overload_worse() || !"overload.worst must be > overload.worse"); + damage += _min(badness, overload_worst() - overload_worse()) * + (overload_max_damage() - damage) / + (overload_worst() - overload_worse()); + } + return damage; +} + +/* message to all factions in ship, start from firstu, end before lastu (may be NULL) */ +static void msg_to_ship_inmates(ship *sh, unit **firstu, unit **lastu, message *msg) { + unit *u, *shipfirst = NULL; + for (u = *firstu; u != *lastu; u = u->next) { + if (u->ship == sh) { + if (shipfirst == NULL) + shipfirst = u; + if (!fval(u->faction, FFL_MARK)) { + fset(u->faction, FFL_MARK); + add_message(&u->faction->msgs, msg); + } + *lastu = u->next; + } + } + if (shipfirst) + *firstu = shipfirst; + for (u = *firstu; u != *lastu; u = u->next) { + freset(u->faction, FFL_MARK); + } + msg_release(msg); +} + static void drifting_ships(region * r) { direction_t d; bool drift = config_get_int("rules.ship.drifting", 1) != 0; + double damage_drift = config_get_flt("rules.ship.damage_drift", 0.02); if (fval(r->terrain, SEA_REGION)) { ship **shp = &r->ships; @@ -710,9 +791,10 @@ static void drifting_ships(region * r) ship *sh = *shp; region *rnext = NULL; region_list *route = NULL; - unit *firstu = NULL, *captain; + unit *firstu = r->units, *lastu = NULL, *captain; int d_offset; direction_t dir = 0; + double ovl; if (sh->type->fishing > 0) { sh->flags |= SF_FISHING; @@ -725,70 +807,66 @@ static void drifting_ships(region * r) } /* Kapitän bestimmen */ - for (captain = r->units; captain; captain = captain->next) { - if (captain->ship != sh) - continue; - if (firstu == NULL) - firstu = captain; - if (effskill(captain, SK_SAILING, r) >= sh->type->cptskill) { - break; - } - } + captain = ship_owner(sh); + if (effskill(captain, SK_SAILING, r) < sh->type->cptskill) + captain = NULL; + /* Kapitän da? Beschädigt? Genügend Matrosen? * Genügend leicht? Dann ist alles OK. */ - assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ if (captain && sh->size == sh->type->construction->maxsize && enoughsailors(sh, crew_skill(sh)) && cansail(r, sh)) { shp = &sh->next; continue; } - /* Auswahl einer Richtung: Zuerst auf Land, dann - * zufällig. Falls unmögliches Resultat: vergiß es. */ - d_offset = rng_int() % MAXDIRECTIONS; - for (d = 0; d != MAXDIRECTIONS; ++d) { - region *rn; - dir = (direction_t)((d + d_offset) % MAXDIRECTIONS); - rn = rconnect(r, dir); - if (rn != NULL && fval(rn->terrain, SAIL_INTO) && check_ship_allowed(sh, rn) > 0) { - rnext = rn; - if (!fval(rnext->terrain, SEA_REGION)) - break; - } - } - - if (rnext == NULL) { - shp = &sh->next; - continue; - } - - /* Das Schiff und alle Einheiten darin werden nun von r - * nach rnext verschoben. Danach eine Meldung. */ - add_regionlist(&route, rnext); - - set_coast(sh, r, rnext); - sh = move_ship(sh, r, rnext, route); - free_regionlist(route); - - if (firstu != NULL) { - unit *u, *lastu = NULL; - message *msg = msg_message("ship_drift", "ship dir", sh, dir); - for (u = firstu; u; u = u->next) { - if (u->ship == sh && !fval(u->faction, FFL_MARK)) { - fset(u->faction, FFL_MARK); - add_message(&u->faction->msgs, msg); - lastu = u->next; + ovl = overload(r, sh); + if (ovl >= overload_start()) { + rnext = NULL; + } else { + /* Auswahl einer Richtung: Zuerst auf Land, dann + * zufällig. Falls unmögliches Resultat: vergiß es. */ + d_offset = rng_int () % MAXDIRECTIONS; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn; + dir = (direction_t)((d + d_offset) % MAXDIRECTIONS); + rn = rconnect(r, dir); + if (rn != NULL && fval(rn->terrain, SAIL_INTO) && check_ship_allowed(sh, rn) > 0) { + rnext = rn; + if (!fval(rnext->terrain, SEA_REGION)) + break; } } - for (u = firstu; u != lastu; u = u->next) { - freset(u->faction, FFL_MARK); + } + + if (rnext != NULL) { + + /* Das Schiff und alle Einheiten darin werden nun von r + * nach rnext verschoben. Danach eine Meldung. */ + add_regionlist(&route, rnext); + + set_coast(sh, r, rnext); + sh = move_ship(sh, r, rnext, route); + free_regionlist(route); + + if (firstu != NULL) { + message *msg = msg_message("ship_drift", "ship dir", sh, dir); + msg_to_ship_inmates(sh, &firstu, &lastu, msg); } - msg_release(msg); } if (sh != NULL) { fset(sh, SF_DRIFTED); + if (ovl >= overload_start()) { + damage_ship(sh, damage_overload(ovl)); + msg_to_ship_inmates(sh, &firstu, &lastu, msg_message("massive_overload", "ship", sh)); + } else + damage_ship(sh, damage_drift); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + msg_to_ship_inmates(sh, &firstu, &lastu, msg_message("shipsink", "ship", sh)); + remove_ship(&sh->region->ships, sh); + } } if (*shp == sh) diff --git a/src/move.test.c b/src/move.test.c index dbbfac5e1..dc16c1b25 100644 --- a/src/move.test.c +++ b/src/move.test.c @@ -279,6 +279,157 @@ static void test_age_trails(CuTest *tc) { test_cleanup(); } +struct drift_fixture { + faction *f; + region *r; + unit *u; + terrain_type *t_ocean; + ship_type *st_boat; + struct locale *lang; + ship *sh; + +}; + +void setup_drift (struct drift_fixture *fix) { + test_cleanup(); + config_set("rules.ship.storms", "0"); + fix->lang = get_or_create_locale("de"); + + test_create_world(); + test_create_shiptype("drifter"); + fix->st_boat = st_get_or_create("drifter"); + fix->st_boat->cabins = 20000; + + fix->u = test_create_unit(fix->f = test_create_faction(0), fix->r=findregion(-1,0)); + assert(fix->r && fix->r->terrain->flags & SAIL_INTO); + set_level(fix->u, SK_SAILING, fix->st_boat->sumskill); + u_set_ship(fix->u, fix->sh = test_create_ship(fix->u->region, fix->st_boat)); + assert(fix->f && fix->u && fix->sh); + fix->f->locale = get_or_create_locale("de"); + +} + +static void test_ship_no_overload(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + + setup_drift(&fix); + + fix.u->number = 2; + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(-1,0)); + CuAssertIntEquals(tc, 0, fix.sh->damage); + + test_cleanup(); +} + +static void test_ship_normal_overload(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + + setup_drift(&fix); + + fix.u->number = 21; + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(0, 0)); + CuAssertIntEquals(tc, 2, ship_damage_percent(fix.sh)); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "ship_drift")); + + test_cleanup(); +} + +static void test_ship_big_overload(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + + setup_drift(&fix); + + fix.u->number = 22; + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(-1, 0)); + CuAssertIntEquals(tc, 5, ship_damage_percent(fix.sh)); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); + + test_cleanup(); +} + +static void test_ship_no_real_overload(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + + setup_drift(&fix); + + fix.u->number = 21; + damage_ship(fix.sh, .80); + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(0, 0)); + CuAssertIntEquals(tc, 82, ship_damage_percent(fix.sh)); + CuAssertPtrEquals(tc, 0, test_find_messagetype(fix.f->msgs, "massive_overload")); + + test_cleanup(); +} + +static void test_ship_ridiculous_overload(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + + setup_drift(&fix); + + fix.u->number = 500; + movement(); + CuAssertIntEquals(tc, 37, ship_damage_percent(fix.sh)); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); + + test_cleanup(); +} + +static void test_ship_ridiculous_overload_no_captain(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + + setup_drift(&fix); + set_level(fix.u, SK_SAILING, 0); + + fix.u->number = 500; + movement(); + CuAssertIntEquals(tc, 37, ship_damage_percent(fix.sh)); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); + + test_cleanup(); +} + +static void test_ship_ridiculous_overload_bad(CuTest *tc) { + struct drift_fixture fix; + + test_cleanup(); + setup_drift(&fix); + + fix.u->number = 500; + config_set("rules.ship.overload.damage.max", "1"); + movement(); + CuAssertTrue(tc, ship_damage_percent(fix.sh) > 99); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); + CuAssertPtrEquals(tc, 0, fix.sh->region); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "shipsink")); + test_cleanup(); +} + +extern double damage_overload(double overload); + +static void test_ship_damage_overload(CuTest *tc) { + CuAssertDblEquals(tc, 0.0, damage_overload(1), ASSERT_DBL_DELTA); + CuAssertDblEquals(tc, 0.05, damage_overload(1.1), ASSERT_DBL_DELTA); + CuAssertDblEquals(tc, 0.05, damage_overload(1.5), ASSERT_DBL_DELTA); + CuAssertDblEquals(tc, 0.21, damage_overload(3.25), ASSERT_DBL_DELTA); + CuAssertDblEquals(tc, 0.37, damage_overload(5), ASSERT_DBL_DELTA); +} + typedef struct traveldir { int no; direction_t dir; @@ -293,7 +444,6 @@ static void test_follow_ship_msg(CuTest * tc) { const ship_type *stype; message *msg; order *ord; - item_type *silver; traveldir *td = NULL; attrib *a; @@ -314,8 +464,7 @@ static void test_follow_ship_msg(CuTest * tc) { assert(ord); unit_addorder(u, ord); - silver = get_resourcetype(R_SILVER)->itype; - i_change(&u->items, silver, 999999); + set_money(u, 999999); a = a_add(&(r->attribs), a_new(&at_shiptrail)); td = (traveldir *)a->data.v; @@ -355,6 +504,14 @@ CuSuite *get_move_suite(void) SUITE_ADD_TEST(suite, test_is_guarded); SUITE_ADD_TEST(suite, test_ship_trails); SUITE_ADD_TEST(suite, test_age_trails); + SUITE_ADD_TEST(suite, test_ship_no_overload); + SUITE_ADD_TEST(suite, test_ship_normal_overload); + SUITE_ADD_TEST(suite, test_ship_no_real_overload); + SUITE_ADD_TEST(suite, test_ship_big_overload); + SUITE_ADD_TEST(suite, test_ship_ridiculous_overload); + SUITE_ADD_TEST(suite, test_ship_ridiculous_overload_bad); + SUITE_ADD_TEST(suite, test_ship_ridiculous_overload_no_captain); + SUITE_ADD_TEST(suite, test_ship_damage_overload); SUITE_ADD_TEST(suite, test_follow_ship_msg); return suite; } diff --git a/src/report.c b/src/report.c index 5431f93df..02a0a5d92 100644 --- a/src/report.c +++ b/src/report.c @@ -1804,8 +1804,7 @@ const unit * captain) WARN_STATIC_BUFFER(); } if (sh->damage) { - int percent = - (sh->damage * 100 + DAMAGE_SCALE - 1) / (sh->size * DAMAGE_SCALE); + int percent = ship_damage_percent(sh); bytes = _snprintf(bufp, size, ", %d%% %s", percent, LOC(f->locale, "nr_damaged")); if (wrptr(&bufp, &size, bytes) != 0) diff --git a/src/tests.c b/src/tests.c index 03d387914..2e7c67314 100644 --- a/src/tests.c +++ b/src/tests.c @@ -133,6 +133,8 @@ ship_type * test_create_shiptype(const char * name) stype->sumskill = 1; stype->minskill = 1; stype->range = 2; + stype->cargo = 1000; + stype->damage = 1; if (!stype->construction) { stype->construction = calloc(1, sizeof(construction)); stype->construction->maxsize = 5; @@ -140,6 +142,12 @@ ship_type * test_create_shiptype(const char * name) stype->construction->reqsize = 1; stype->construction->skill = SK_SHIPBUILDING; } + + stype->coasts = + (terrain_type **)malloc(sizeof(terrain_type *)*2); + stype->coasts[0] = get_or_create_terrain("plain"); + stype->coasts[1] = NULL; + if (default_locale) { locale_setstring(default_locale, name, name); } @@ -225,6 +233,7 @@ void test_create_world(void) locale_setstring(loc, keyword(K_RESERVE), "RESERVIEREN"); locale_setstring(loc, "money", "SILBER"); init_resources(); + get_resourcetype(R_SILVER)->itype->weight = 1; test_create_horse(); @@ -236,7 +245,7 @@ void test_create_world(void) test_create_itemtype("iron"); test_create_itemtype("stone"); - t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | FLY_INTO); + t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | SAIL_INTO | FLY_INTO); t_plain->size = 1000; t_plain->max_road = 100; t_ocean = test_create_terrain("ocean", SEA_REGION | SAIL_INTO | SWIM_INTO | FLY_INTO); diff --git a/src/tests.h b/src/tests.h index f0b64729b..b69b5ff0e 100644 --- a/src/tests.h +++ b/src/tests.h @@ -8,6 +8,8 @@ extern "C" { #endif + #define ASSERT_DBL_DELTA 0.001 + struct region; struct unit; struct faction;