From 3a18384ddbb5588e38cbf7c2fd04bd94b9814652 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Sun, 15 Nov 2015 02:20:55 +0100 Subject: [PATCH] overfull ships do not drift but take damage http://bugs.eressea.de/view.php?id=2157 removed exploit: ships more than 2 times over their ship type's capacity do not drift but take 30% to 100% damage --- res/core/messages.xml | 7 ++ scripts/tests/e3/rules.lua | 24 ++++++ src/bind_ship.c | 17 +++++ src/kernel/ship.c | 2 +- src/move.c | 147 ++++++++++++++++++++++++------------- src/move.test.c | 104 ++++++++++++++++++++++++++ src/tests.c | 10 ++- src/tests.h | 2 + 8 files changed, 260 insertions(+), 53 deletions(-) diff --git a/res/core/messages.xml b/res/core/messages.xml index a3d7ad6e8..903550107 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/scripts/tests/e3/rules.lua b/scripts/tests/e3/rules.lua index 28b14fe10..b92df87b0 100644 --- a/scripts/tests/e3/rules.lua +++ b/scripts/tests/e3/rules.lua @@ -789,3 +789,27 @@ function test_volcanooutbreak_message() assert_not_equal("", msg:render("de")) assert_not_equal("", msg:render("en")) end + +function test_ship_massive() + set_rule("rules.ship.drifting", "1") + set_rule("rules.ship.damage_drift", "0.02") + local r = region.create(0,0, "ocean") + region.create(1,0, "plain") + region.create(0,1, "ocean") + local f = faction.create("massive@eressea.de", "human", "de") + + local s1 = ship.create(r, "cutter") + s1.damage = 390 + local u1 = unit.create(f, r, 10) + u1.ship = s1 +-- u1:set_skill("sailing", 10) + u1:clear_orders() + u1:add_order("NACH o") + + update_owners() + process_orders() + write_reports() + assert_equal(990, s1.damage) + + set_rule("rules.ship.drifting", "0") +end 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 c77a10bd4..b1c0e1725 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; } diff --git a/src/move.c b/src/move.c index 00752c3cf..ccb58ac9a 100644 --- a/src/move.c +++ b/src/move.c @@ -80,6 +80,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include +#include int *storms; @@ -473,6 +474,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,6 +721,37 @@ static float damage_drift(void) return value; } +static float damage_overload(void) +{ + static float value = -1.0F; + if (value < 0) { + value = (float)get_param_flt(global.parameters, "rules.ship.damage_overload", 0.3F); + } + return value; +} + +/* 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; @@ -710,9 +763,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,73 +779,64 @@ 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 >= 2) { + 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); - - damage_ship(sh, damage_drift()); + if (ovl >= 2) { + damage_ship(sh, (ovl - 1) * damage_overload()); + 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); } } diff --git a/src/move.test.c b/src/move.test.c index 5c24ca72e..d0a91b9b9 100644 --- a/src/move.test.c +++ b/src/move.test.c @@ -295,6 +295,104 @@ 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(); + set_param(&global.parameters, "rules.ship.storms", "0"); + fix->lang = get_or_create_locale("de"); + + test_create_world(); + + fix->st_boat = st_get_or_create("boat"); + fix->st_boat->cabins = 2000; + 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; + setup_drift(&fix); + + fix.u->number = 2; + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(-1,0)); + CuAssertIntEquals(tc, 0, fix.sh->damage); +} + +static void test_ship_normal_overload(CuTest *tc) { + struct drift_fixture fix; + setup_drift(&fix); + + fix.u->number = 3; + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(0, 0)); + CuAssertDblEquals(tc, (double) (fix.sh->size * DAMAGE_SCALE * .02), (double ) fix.sh->damage, ASSERT_DBL_DELTA); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "ship_drift")); +} + +static void test_ship_big_overload(CuTest *tc) { + struct drift_fixture fix; + setup_drift(&fix); + + fix.u->number = 4; + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(-1, 0)); + CuAssertDblEquals(tc, (double) (fix.sh->size * DAMAGE_SCALE * .3), (double ) fix.sh->damage, ASSERT_DBL_DELTA); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); +} + +static void test_ship_no_real_overload(CuTest *tc) { + struct drift_fixture fix; + setup_drift(&fix); + + fix.u->number = 3; + damage_ship(fix.sh, .80); + movement(); + CuAssertPtrEquals(tc, fix.u->region, findregion(0, 0)); + CuAssertDblEquals(tc, (double) (fix.sh->size * DAMAGE_SCALE * .82), (double ) fix.sh->damage, ASSERT_DBL_DELTA); + CuAssertPtrEquals(tc, 0, test_find_messagetype(fix.f->msgs, "massive_overload")); +} + +static void test_ship_ridiculous_overload(CuTest *tc) { + struct drift_fixture fix; + setup_drift(&fix); + + fix.u->number = 100; + movement(); + CuAssertTrue(tc, fix.sh->size * DAMAGE_SCALE * .2 < fix.sh->damage); + CuAssertPtrEquals(tc, 0, fix.sh->region); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "shipsink")); +} + +static void test_ship_ridiculous_overload_no_captain(CuTest *tc) { + struct drift_fixture fix; + setup_drift(&fix); + set_level(fix.u, SK_SAILING, 0); + + fix.u->number = 100; + movement(); + CuAssertTrue(tc, fix.sh->size * DAMAGE_SCALE * .2 < fix.sh->damage); + CuAssertPtrEquals(tc, 0, fix.sh->region); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "massive_overload")); + CuAssertPtrNotNull(tc, test_find_messagetype(fix.f->msgs, "shipsink")); +} + CuSuite *get_move_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -309,5 +407,11 @@ 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_no_captain); return suite; } diff --git a/src/tests.c b/src/tests.c index e7518211e..264416458 100644 --- a/src/tests.c +++ b/src/tests.c @@ -135,6 +135,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; @@ -142,6 +144,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); } @@ -238,7 +246,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); + t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | SAIL_INTO); t_plain->size = 1000; t_plain->max_road = 100; t_ocean = test_create_terrain("ocean", SEA_REGION | SAIL_INTO | SWIM_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;