diff --git a/res/core/de/strings.xml b/res/core/de/strings.xml index 75f57a6b0..c9a2ae616 100644 --- a/res/core/de/strings.xml +++ b/res/core/de/strings.xml @@ -7574,4 +7574,22 @@ + + Groaamm... + + + Tschrrrk... + Tshrrrk... + + + Schhhhh... + Shhhhhh... + + + Roaarrr... + + + Chrrr... + + diff --git a/res/core/messages.xml b/res/core/messages.xml index 15de4a153..bb57fd32d 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -7293,8 +7293,8 @@ - "$unit($unit) konnte durch einen Heiltrank überleben." - "$unit($unit) was saved by a healing potion." + "Eine Person von $unit($unit) konnte durch einen Heiltrank überleben." + "A fighter of $unit($unit) was saved by a healing potion." @@ -8441,4 +8441,15 @@ "$unit($unit) in $region($region): '$order($command)' - Heroes cannot recruit." + + + + + + + + "$unit($dragon): \"$localize($growl) $if($eq($number,1), "Ich rieche", "Wir riechen") etwas in $region($target)\"." + "$unit($dragon): \"$localize($growl) $if($eq($number,1), "I smell", "We smell") something in $region($target)\"." + + diff --git a/res/core/ships.xml b/res/core/ships.xml index 6c6821be4..0ce254328 100644 --- a/res/core/ships.xml +++ b/res/core/ships.xml @@ -1,7 +1,6 @@ - @@ -9,7 +8,6 @@ - @@ -17,7 +15,6 @@ - @@ -25,7 +22,6 @@ - @@ -33,7 +29,6 @@ - @@ -48,7 +43,6 @@ - @@ -65,7 +59,6 @@ - diff --git a/res/e3a/ships.xml b/res/e3a/ships.xml index 1174eda8e..b8c5f0197 100644 --- a/res/e3a/ships.xml +++ b/res/e3a/ships.xml @@ -1,7 +1,6 @@ - @@ -17,7 +16,6 @@ - @@ -28,7 +26,6 @@ - @@ -44,7 +41,6 @@ - @@ -61,7 +57,6 @@ - @@ -78,7 +73,6 @@ - @@ -89,7 +83,6 @@ - @@ -98,7 +91,6 @@ - @@ -109,7 +101,6 @@ - @@ -120,7 +111,6 @@ - @@ -133,7 +123,6 @@ - @@ -145,7 +134,6 @@ - diff --git a/res/ships/boat.xml b/res/ships/boat.xml index 28d48392f..91852de92 100644 --- a/res/ships/boat.xml +++ b/res/ships/boat.xml @@ -1,6 +1,5 @@ - diff --git a/src/alchemy.c b/src/alchemy.c index 5419be4ee..5e369a3e1 100644 --- a/src/alchemy.c +++ b/src/alchemy.c @@ -318,6 +318,10 @@ static int a_readeffect(attrib * a, void *owner, struct gamedata *data) if (rtype == NULL || rtype->ptype == NULL || power <= 0) { return AT_READ_FAIL; } + if (rtype->ptype==oldpotiontype[P_HEAL]) { + // healing potions used to have long-term effects + return AT_READ_FAIL; + } edata->type = rtype->ptype; edata->value = power; return AT_READ_OK; diff --git a/src/battle.c b/src/battle.c index 231d7f2e1..217f73c34 100644 --- a/src/battle.c +++ b/src/battle.c @@ -690,7 +690,7 @@ static int CavalryBonus(const unit * u, troop enemy, int type) int skl = effskill(u, SK_RIDING, 0); /* only half against trolls */ if (skl > 0) { - if (type == BONUS_DAMAGE) { + if (type == BONUS_SKILL) { int dmg = _min(skl, 8); if (u_race(enemy.fighter->unit) == get_race(RC_TROLL)) { dmg = dmg / 4; @@ -1156,7 +1156,6 @@ terminate(troop dt, troop at, int type, const char *damage, bool missile) unit *au = af->unit; unit *du = df->unit; battle *b = df->side->battle; - int heiltrank = 0; /* Schild */ side *ds = df->side; @@ -1289,7 +1288,7 @@ terminate(troop dt, troop at, int type, const char *damage, bool missile) } } - assert(dt.index < du->number); + assert(dt.index >= 0 && dt.index < du->number); if (rda>0) { df->person[dt.index].hp -= rda; if (u_race(au) == get_race(RC_DAEMON)) { @@ -1314,36 +1313,23 @@ terminate(troop dt, troop at, int type, const char *damage, bool missile) df->person[dt.index].defence--; } } - df->person[dt.index].flags = (df->person[dt.index].flags & ~FL_SLEEPING); return false; } /* Sieben Leben */ if (u_race(du) == get_race(RC_CAT) && (chance(1.0 / 7))) { - assert(dt.index >= 0 && dt.index < du->number); df->person[dt.index].hp = unit_max_hp(du); return false; } - /* Heiltrank schluerfen und hoffen */ - if (oldpotiontype[P_HEAL]) { - if (get_effect(du, oldpotiontype[P_HEAL]) > 0) { - change_effect(du, oldpotiontype[P_HEAL], -1); - heiltrank = 1; - } - else if (i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0) { + if (oldpotiontype[P_HEAL] && !fval(&df->person[dt.index], FL_HEALING_USED)) { + if (i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0) { i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); - change_effect(du, oldpotiontype[P_HEAL], 3); - heiltrank = 1; - } - if (heiltrank && (chance(0.50))) { - { - message *m = msg_message("battle::potionsave", "unit", du); - message_faction(b, du->faction, m); - msg_release(m); - } - assert(dt.index >= 0 && dt.index < du->number); - df->person[dt.index].hp = u_race(du)->hitpoints; + message *m = msg_message("battle::potionsave", "unit", du); + message_faction(b, du->faction, m); + msg_release(m); + fset(&df->person[dt.index], FL_HEALING_USED); + df->person[dt.index].hp = u_race(du)->hitpoints * 5; /* give the person a buffer */ return false; } } @@ -2384,7 +2370,7 @@ static void add_tactics(tactics * ta, fighter * fig, int value) ta->value = value; } -static double horsebonus(const unit * u) +static double horse_fleeing_bonus(const unit * u) { const item_type *it_horse, *it_elvenhorse, *it_charger; int n1 = 0, n2 = 0, n3 = 0; @@ -2392,8 +2378,6 @@ static double horsebonus(const unit * u) int skl = effskill(u, SK_RIDING, 0); const resource_type *rtype; - if (skl < 1) return 0.0; - it_horse = ((rtype = get_resourcetype(R_HORSE)) != NULL) ? rtype->itype : 0; it_elvenhorse = ((rtype = get_resourcetype(R_UNICORN)) != NULL) ? rtype->itype : 0; it_charger = ((rtype = get_resourcetype(R_CHARGER)) != NULL) ? rtype->itype : 0; @@ -2410,9 +2394,9 @@ static double horsebonus(const unit * u) } if (skl >= 5 && n3 >= u->number) return 0.30; - if (skl >= 3 && n2 + n3 >= u->number) + if (skl >= 2 && n2 + n3 >= u->number) return 0.20; - if (skl >= 1 && n1 + n2 + n3 >= u->number) + if (n1 + n2 + n3 >= u->number) return 0.10; return 0.0F; } @@ -2424,7 +2408,7 @@ double fleechance(unit * u) /* Einheit u versucht, dem Getümmel zu entkommen */ c += (effskill(u, SK_STEALTH, 0) * 0.05); - c += horsebonus(u); + c += horse_fleeing_bonus(u); if (u_race(u) == get_race(RC_HALFLING)) { c += 0.20; diff --git a/src/battle.h b/src/battle.h index 4ea3834d9..3e156070d 100644 --- a/src/battle.h +++ b/src/battle.h @@ -137,6 +137,7 @@ extern "C" { #define FL_SLEEPING 16 #define FL_STUNNED 32 /* eine Runde keinen Angriff */ #define FL_HIT 64 /* the person at attacked */ +#define FL_HEALING_USED 128 /* has used a healing potion */ typedef struct troop { struct fighter *fighter; diff --git a/src/kernel/jsonconf.test.c b/src/kernel/jsonconf.test.c index 63c5fe0ff..858c811b9 100644 --- a/src/kernel/jsonconf.test.c +++ b/src/kernel/jsonconf.test.c @@ -458,7 +458,7 @@ static void test_terrains(CuTest * tc) "\"size\": 4000, " "\"road\": 50, " "\"seed\": 3, " - "\"flags\" : [ \"forbidden\", \"arctic\", \"cavalry\", \"sea\", \"forest\", \"land\", \"sail\", \"fly\", \"swim\", \"walk\" ] } }}"; + "\"flags\" : [ \"forbidden\", \"arctic\", \"cavalry\", \"sea\", \"forest\", \"land\", \"fly\", \"swim\", \"walk\" ] } }}"; const terrain_type *ter; cJSON *json = cJSON_Parse(data); @@ -470,7 +470,7 @@ static void test_terrains(CuTest * tc) json_config(json); ter = get_terrain("plain"); CuAssertPtrNotNull(tc, ter); - CuAssertIntEquals(tc, ARCTIC_REGION | LAND_REGION | SEA_REGION | FOREST_REGION | CAVALRY_REGION | FORBIDDEN_REGION | FLY_INTO | WALK_INTO | SWIM_INTO | SAIL_INTO, ter->flags); + CuAssertIntEquals(tc, ARCTIC_REGION | LAND_REGION | SEA_REGION | FOREST_REGION | CAVALRY_REGION | FORBIDDEN_REGION | FLY_INTO | WALK_INTO | SWIM_INTO , ter->flags); CuAssertIntEquals(tc, 4000, ter->size); CuAssertIntEquals(tc, 50, ter->max_road); CuAssertIntEquals(tc, 3, ter->distribution); diff --git a/src/kernel/terrain.h b/src/kernel/terrain.h index 931a74861..3d1491867 100644 --- a/src/kernel/terrain.h +++ b/src/kernel/terrain.h @@ -31,7 +31,6 @@ extern "C" { #define CAVALRY_REGION (1<<4) /* riding in combat is possible */ /* Achtung: SEA_REGION ist nicht das Gegenteil von LAND_REGION. Die zwei schliessen sich nichtmal aus! */ #define FORBIDDEN_REGION (1<<5) /* unpassierbare Blockade-struct region */ -#define SAIL_INTO (1<<6) /* man darf hierhin segeln */ #define FLY_INTO (1<<7) /* man darf hierhin fliegen */ #define SWIM_INTO (1<<8) /* man darf hierhin schwimmen */ #define WALK_INTO (1<<9) /* man darf hierhin laufen */ diff --git a/src/laws.c b/src/laws.c index 79889e7da..203970dc4 100755 --- a/src/laws.c +++ b/src/laws.c @@ -2213,26 +2213,13 @@ int send_cmd(unit * u, struct order *ord) return 0; } -static bool display_item(faction * f, unit * u, const item_type * itype) +static void display_item(unit * u, const item_type * itype) { + faction * f = u->faction; const char *name; const char *key; const char *info; - if (u != NULL) { - int i = i_get(u->items, itype); - if (i == 0) { - if (u->region->land != NULL) { - i = i_get(u->region->land->items, itype); - } - if (i == 0) { - i = i_get(u->faction->items, itype); - if (i == 0) - return false; - } - } - } - name = resourcename(itype->rtype, 0); key = mkname("iteminfo", name); info = locale_getstring(f->locale, key); @@ -2242,23 +2229,13 @@ static bool display_item(faction * f, unit * u, const item_type * itype) } ADDMSG(&f->msgs, msg_message("displayitem", "weight item description", itype->weight, itype->rtype, info)); - - return true; } -static bool display_potion(faction * f, unit * u, const potion_type * ptype) +static void display_potion(unit * u, const potion_type * ptype) { + faction * f = u->faction; attrib *a; - if (ptype == NULL) - return false; - else { - int i = i_get(u->items, ptype->itype); - if (i == 0 && 2 * ptype->level > effskill(u, SK_ALCHEMY, 0)) { - return false; - } - } - a = a_find(f->attribs, &at_showitem); while (a && a->data.v != ptype) a = a->next; @@ -2266,12 +2243,11 @@ static bool display_potion(faction * f, unit * u, const potion_type * ptype) a = a_add(&f->attribs, a_new(&at_showitem)); a->data.v = (void *)ptype->itype; } - - return true; } -static bool display_race(faction * f, unit * u, const race * rc) +static void display_race(unit * u, const race * rc) { + faction * f = u->faction; const char *name, *key; const char *info; int a, at_count; @@ -2279,8 +2255,6 @@ static bool display_race(faction * f, unit * u, const race * rc) size_t size = sizeof(buf) - 1; size_t bytes; - if (u && u_race(u) != rc) - return false; name = rc_name_s(rc, NAME_SINGULAR); bytes = slprintf(bufp, size, "%s: ", LOC(f->locale, name)); @@ -2294,7 +2268,7 @@ static bool display_race(faction * f, unit * u, const race * rc) info = LOC(f->locale, mkname("raceinfo", "no_info")); } - bufp = STRLCPY(bufp, info, size); + if (info) bufp = STRLCPY(bufp, info, size); /* hp_p : Trefferpunkte */ bytes = @@ -2412,17 +2386,72 @@ static bool display_race(faction * f, unit * u, const race * rc) *bufp = 0; addmessage(0, f, buf, MSG_EVENT, ML_IMPORTANT); +} - return true; +static void reshow_other(unit * u, struct order *ord, const char *s) { + int err = 21; + + if (s) { + const spell *sp = 0; + const item_type *itype; + const race *rc; + /* check if it's an item */ + itype = finditemtype(s, u->faction->locale); + sp = unit_getspell(u, s, u->faction->locale); + rc = findrace(s, u->faction->locale); + + if (itype) { + // if this is a potion, we need the right alchemy skill + int i = i_get(u->items, itype); + + err = 36; // we do not have this item? + if (i <= 0) { + // we don't have the item, but it may be a potion that we know + const potion_type *ptype = resource2potion(item2resource(itype)); + if (ptype) { + if (2 * ptype->level > effskill(u, SK_ALCHEMY, 0)) { + itype = NULL; + } + } else { + itype = NULL; + } + } + } + + if (itype) { + const potion_type *ptype = itype->rtype->ptype; + if (ptype) { + display_potion(u, ptype); + } + else { + display_item(u, itype); + } + return; + } + + if (sp) { + attrib *a = a_find(u->faction->attribs, &at_seenspell); + while (a != NULL && a->type == &at_seenspell && a->data.v != sp) { + a = a->next; + } + if (a != NULL) { + a_remove(&u->faction->attribs, a); + } + return; + } + + if (rc && u_race(u) == rc) { + display_race(u, rc); + return; + } + } + cmistake(u, ord, err, MSG_EVENT); } static void reshow(unit * u, struct order *ord, const char *s, param_t p) { int skill, c; const potion_type *ptype; - const item_type *itype; - const spell *sp = 0; - const race *rc; switch (p) { case P_ZAUBER: @@ -2433,48 +2462,15 @@ static void reshow(unit * u, struct order *ord, const char *s, param_t p) c = 0; for (ptype = potiontypes; ptype != NULL; ptype = ptype->next) { if (ptype->level * 2 <= skill) { - c += display_potion(u->faction, u, ptype); + display_potion(u, ptype); + ++c; } } if (c == 0) cmistake(u, ord, 285, MSG_EVENT); break; case NOPARAM: - if (s) { - /* check if it's an item */ - itype = finditemtype(s, u->faction->locale); - if (itype != NULL) { - ptype = resource2potion(item2resource(itype)); - if (ptype != NULL) { - if (display_potion(u->faction, u, ptype)) - break; - } - else { - if (!display_item(u->faction, u, itype)) - cmistake(u, ord, 36, MSG_EVENT); - - break; - } - } - /* try for a spell */ - sp = unit_getspell(u, s, u->faction->locale); - if (sp) { - attrib *a = a_find(u->faction->attribs, &at_seenspell); - while (a != NULL && a->type == &at_seenspell && a->data.v != sp) { - a = a->next; - } - if (a != NULL) { - a_remove(&u->faction->attribs, a); - } - break; - } - /* last, check if it's a race. */ - rc = findrace(s, u->faction->locale); - if (rc != NULL && display_race(u->faction, u, rc)) { - break; - } - } - cmistake(u, ord, 21, MSG_EVENT); + reshow_other(u, ord, s); break; default: cmistake(u, ord, 222, MSG_EVENT); diff --git a/src/laws.test.c b/src/laws.test.c index 1b2136db7..623717619 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -27,6 +27,7 @@ #include #include +#include static void test_new_building_can_be_renamed(CuTest * tc) { @@ -1251,6 +1252,83 @@ static void test_show_without_item(CuTest *tc) test_cleanup(); } +static void test_show_elf(CuTest *tc) { + order *ord; + race * rc; + unit *u; + struct locale *loc; + message * msg; + + test_cleanup(); + + mt_register(mt_new_va("msg_event", "string:string", 0)); + rc = test_create_race("elf"); + test_create_itemtype("elvenhorse"); + + loc = get_or_create_locale("de"); + locale_setstring(loc, "elvenhorse", "Elfenpferd"); + locale_setstring(loc, "elvenhorse_p", "Elfenpferde"); + locale_setstring(loc, "race::elf_p", "Elfen"); + locale_setstring(loc, "race::elf", "Elf"); + init_locale(loc); + + CuAssertPtrNotNull(tc, finditemtype("elf", loc)); + CuAssertPtrNotNull(tc, findrace("elf", loc)); + + u = test_create_unit(test_create_faction(rc), test_create_region(0, 0, 0)); + u->faction->locale = loc; + ord = create_order(K_RESHOW, loc, "Elf"); + reshow_cmd(u, ord); + CuAssertTrue(tc, test_find_messagetype(u->faction->msgs, "error36") == NULL); + msg = test_find_messagetype(u->faction->msgs, "msg_event"); + CuAssertPtrNotNull(tc, msg); + CuAssertTrue(tc, memcmp("Elf:", msg->parameters[0].v, 4) == 0); + test_clear_messages(u->faction); + free_order(ord); + test_cleanup(); +} + +static void test_show_race(CuTest *tc) { + order *ord; + race * rc; + unit *u; + struct locale *loc; + message * msg; + + test_cleanup(); + + mt_register(mt_new_va("msg_event", "string:string", 0)); + test_create_race("human"); + rc = test_create_race("elf"); + + loc = get_or_create_locale("de"); + locale_setstring(loc, "race::elf_p", "Elfen"); + locale_setstring(loc, "race::elf", "Elf"); + locale_setstring(loc, "race::human_p", "Menschen"); + locale_setstring(loc, "race::human", "Mensch"); + init_locale(loc); + u = test_create_unit(test_create_faction(rc), test_create_region(0, 0, 0)); + u->faction->locale = loc; + + ord = create_order(K_RESHOW, loc, "Mensch"); + reshow_cmd(u, ord); + CuAssertTrue(tc, test_find_messagetype(u->faction->msgs, "error21") != NULL); + CuAssertTrue(tc, test_find_messagetype(u->faction->msgs, "msg_event") == NULL); + test_clear_messages(u->faction); + free_order(ord); + + ord = create_order(K_RESHOW, loc, "Elf"); + reshow_cmd(u, ord); + CuAssertTrue(tc, test_find_messagetype(u->faction->msgs, "error21") == NULL); + msg = test_find_messagetype(u->faction->msgs, "msg_event"); + CuAssertPtrNotNull(tc, msg); + CuAssertTrue(tc, memcmp("Elf:", msg->parameters[0].v, 4) == 0); + test_clear_messages(u->faction); + free_order(ord); + + test_cleanup(); +} + static int low_wage(const region * r, const faction * f, const race * rc, int in_turn) { return 1; } @@ -1386,6 +1464,8 @@ CuSuite *get_laws_suite(void) SUITE_ADD_TEST(suite, test_name_building); SUITE_ADD_TEST(suite, test_name_ship); SUITE_ADD_TEST(suite, test_show_without_item); + SUITE_ADD_TEST(suite, test_show_elf); + SUITE_ADD_TEST(suite, test_show_race); SUITE_ADD_TEST(suite, test_immigration); SUITE_ADD_TEST(suite, test_demon_hunger); diff --git a/src/modules/score.c b/src/modules/score.c index 74eb0898b..2fd3cf230 100644 --- a/src/modules/score.c +++ b/src/modules/score.c @@ -167,7 +167,7 @@ void score(void) fprintf(scoreFP, "(%s) ", score); fprintf(scoreFP, "%30.30s (%3.3s) %5s (%3d)\n", f->name, - rc_name_s(f->race, NAME_SINGULAR), + f->race->_name, factionid(f), f->age); } diff --git a/src/monsters.c b/src/monsters.c index 674ab0999..2fc2fcde3 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -547,21 +547,23 @@ static order *monster_seeks_target(region * r, unit * u) } #endif -static const char *random_growl(void) +void random_growl(const unit *u, region *target, int rand) { - switch (rng_int() % 5) { - case 0: - return "Groammm"; - case 1: - return "Roaaarrrr"; - case 2: - return "Chhhhhhhhhh"; - case 3: - return "Tschrrrkk"; - case 4: - return "Schhhh"; + const struct locale *lang = u->faction->locale; + const char *growl; + switch(rand){ + case 1: growl = "growl1"; break; + case 2: growl = "growl2"; break; + case 3: growl = "growl3"; break; + case 4: growl = "growl4"; break; + default: growl = "growl0"; + } + + + if (rname(target, lang)) { + message *msg = msg_message("dragon_growl", "dragon number target growl", u, u->number, target, growl); + ADDMSG(&u->region->msgs, msg); } - return ""; } extern struct attrib_type at_direction; @@ -707,17 +709,7 @@ static order *plan_dragon(unit * u) reduce_weight(u); } if (rng_int() % 100 < 15) { - const struct locale *lang = u->faction->locale; - /* do a growl */ - if (rname(tr, lang)) { - addlist(&u->orders, - create_order(K_MAIL, lang, "%s '%s... %s %s %s'", - LOC(lang, parameters[P_REGION]), - random_growl(), - u->number == - 1 ? "Ich rieche" : "Wir riechen", - "etwas in", rname(tr, u->faction->locale))); - } + random_growl(u, tr, rng_int() % 5); } } else { diff --git a/src/monsters.test.c b/src/monsters.test.c index a7a4d9b18..dd69e6bdb 100644 --- a/src/monsters.test.c +++ b/src/monsters.test.c @@ -12,10 +12,13 @@ #include "monster.h" #include "guard.h" +#include "reports.h" #include "skill.h" #include "study.h" #include +#include +#include #include #include @@ -55,7 +58,7 @@ static void create_monsters(faction **player, faction **monsters, unit **u, unit fset(*monsters, FFL_NOIDLEOUT); assert(fval(*monsters, FFL_NPC) && fval((*monsters)->race, RCF_UNARMEDGUARD) && fval((*monsters)->race, RCF_NPC) && fval(*monsters, FFL_NOIDLEOUT)); - test_create_region(-1, 0, test_create_terrain("ocean", SEA_REGION | SAIL_INTO | SWIM_INTO | FLY_INTO)); + test_create_region(-1, 0, test_create_terrain("ocean", SEA_REGION | SWIM_INTO | FLY_INTO)); test_create_region(1, 0, 0); r = test_create_region(0, 0, 0); @@ -185,11 +188,14 @@ static void test_dragon_attacks_the_rich(CuTest * tc) test_cleanup(); } +extern void random_growl(const unit *u, region *tr, int rand); + static void test_dragon_moves(CuTest * tc) { faction *f, *f2; region *r; unit *u, *m; + struct message *msg; create_monsters(&f, &f2, &u, &m); rsetmoney(findregion(1, 0), 1000); @@ -202,6 +208,18 @@ static void test_dragon_moves(CuTest * tc) plan_monsters(f2); CuAssertPtrNotNull(tc, find_order("move east", m)); + + mt_register(mt_new_va("dragon_growl", "dragon:unit", "number:int", "target:region", "growl:string", 0)); + + random_growl(m, findregion(1, 0), 3); + + msg = test_get_last_message(r->msgs); + assert_message(tc, msg, "dragon_growl", 4); + assert_pointer_parameter(tc, msg, 0, m); + assert_int_parameter(tc, msg, 1, 1); + assert_pointer_parameter(tc, msg, 2, findregion(1,0)); + assert_string_parameter(tc, msg, 3, "growl3"); + test_cleanup(); } diff --git a/src/move.c b/src/move.c index e602b073c..e7ca523b5 100644 --- a/src/move.c +++ b/src/move.c @@ -677,12 +677,6 @@ int check_ship_allowed(struct ship *sh, const region * r) } if (is_freezing(u)) { - unit *captain = ship_owner(sh); - if (captain) { - ADDMSG(&captain->faction->msgs, - msg_message("detectforbidden", "unit region", u, r)); - } - return SA_NO_INSECT; } } @@ -783,9 +777,26 @@ static void msg_to_ship_inmates(ship *sh, unit **firstu, unit **lastu, message * msg_release(msg); } +region * drift_target(ship *sh) { + int d, d_offset = rng_int() % MAXDIRECTIONS; + region *rnext = NULL; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn; + direction_t dir = (direction_t)((d + d_offset) % MAXDIRECTIONS); + rn = rconnect(sh->region, dir); + if (rn != NULL && check_ship_allowed(sh, rn) >= 0) { + rnext = rn; + if (!fval(rnext->terrain, SEA_REGION)) { + // prefer drifting towards non-ocean regions + break; + } + } + } + return rnext; +} + 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); @@ -796,7 +807,6 @@ static void drifting_ships(region * r) region *rnext = NULL; region_list *route = NULL; unit *firstu = r->units, *lastu = NULL, *captain; - int d_offset; direction_t dir = 0; double ovl; @@ -831,17 +841,7 @@ static void drifting_ships(region * r) } 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; - } - } + rnext = drift_target(sh); } if (rnext != NULL) { @@ -1946,7 +1946,10 @@ sail(unit * u, order * ord, bool move_on_land, region_list ** routep) reason = check_ship_allowed(sh, next_point); if (reason < 0) { /* for some reason or another, we aren't allowed in there.. */ - if (check_leuchtturm(current_point, NULL) || reason == SA_NO_INSECT) { + if (reason == SA_NO_INSECT) { + ADDMSG(&f->msgs, msg_message("detectforbidden", "unit region", u, sh->region)); + } + else if (check_leuchtturm(current_point, NULL)) { ADDMSG(&f->msgs, msg_message("sailnolandingstorm", "ship region", sh, next_point)); } else { diff --git a/src/move.h b/src/move.h index 0417dcec5..9c5893629 100644 --- a/src/move.h +++ b/src/move.h @@ -77,12 +77,13 @@ extern "C" { void move_cmd(struct unit * u, struct order * ord, bool move_on_land); int follow_ship(struct unit * u, struct order * ord); -#define SA_HARBOUR 2 -#define SA_COAST 1 +#define SA_HARBOUR 1 +#define SA_COAST 0 #define SA_NO_INSECT -1 #define SA_NO_COAST -2 int check_ship_allowed(struct ship *sh, const struct region * r); + struct region * drift_target(struct ship *sh); #ifdef __cplusplus } #endif diff --git a/src/move.test.c b/src/move.test.c index 4c0a07bc5..e974a7b69 100644 --- a/src/move.test.c +++ b/src/move.test.c @@ -35,8 +35,8 @@ static void test_ship_not_allowed_in_coast(CuTest * tc) ship_type *stype; test_cleanup(); - ttype = test_create_terrain("glacier", LAND_REGION | ARCTIC_REGION | WALK_INTO | SAIL_INTO); - otype = test_create_terrain("ocean", SEA_REGION | SAIL_INTO); + ttype = test_create_terrain("glacier", LAND_REGION | ARCTIC_REGION | WALK_INTO); + otype = test_create_terrain("ocean", SEA_REGION); stype = test_create_shiptype("derp"); free(stype->coasts); stype->coasts = (struct terrain_type **)calloc(2, sizeof(struct terrain_type *)); @@ -69,7 +69,7 @@ static void setup_harbor(move_fixture *mf) { test_cleanup(); - ttype = test_create_terrain("glacier", LAND_REGION | ARCTIC_REGION | WALK_INTO | SAIL_INTO); + ttype = test_create_terrain("glacier", LAND_REGION | ARCTIC_REGION | WALK_INTO); btype = test_create_buildingtype("harbour"); sh = test_create_ship(0, 0); @@ -232,7 +232,7 @@ static void test_ship_trails(CuTest *tc) { region_list *route = 0; test_cleanup(); - otype = test_create_terrain("ocean", SEA_REGION | SAIL_INTO); + otype = test_create_terrain("ocean", SEA_REGION); r1 = test_create_region(0, 0, otype); r2 = test_create_region(1, 0, otype); r3 = test_create_region(2, 0, otype); @@ -298,7 +298,7 @@ void setup_drift (struct drift_fixture *fix) { 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); + assert(fix->r); 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); @@ -498,6 +498,24 @@ static void test_follow_ship_msg(CuTest * tc) { test_cleanup(); } +static void test_drifting_ships(CuTest *tc) { + ship *sh; + region *r1, *r2, *r3; + terrain_type *t_ocean, *t_plain; + ship_type *st_boat; + test_cleanup(); + t_ocean = test_create_terrain("ocean", SEA_REGION); + t_plain = test_create_terrain("plain", LAND_REGION); + r1 = test_create_region(0, 0, t_ocean); + r2 = test_create_region(1, 0, t_ocean); + st_boat = test_create_shiptype("boat"); + sh = test_create_ship(r1, st_boat); + CuAssertPtrEquals(tc, r2, drift_target(sh)); + r3 = test_create_region(-1, 0, t_plain); + CuAssertPtrEquals(tc, r3, drift_target(sh)); + test_cleanup(); +} + CuSuite *get_move_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -521,5 +539,6 @@ CuSuite *get_move_suite(void) 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); + SUITE_ADD_TEST(suite, test_drifting_ships); return suite; } diff --git a/src/piracy.c b/src/piracy.c index e8864faa9..05a744cb9 100644 --- a/src/piracy.c +++ b/src/piracy.c @@ -151,7 +151,7 @@ void piracy_cmd(unit * u, order *ord) // TODO this could still result in an illegal movement order (through a wall or whatever) // which will be prevented by move_cmd below if (rc && - ((sh && fval(rc->terrain, SAIL_INTO) && can_takeoff(sh, r, rc)) + ((sh && !fval(rc->terrain, FORBIDDEN_REGION) && can_takeoff(sh, r, rc)) || (canswim(u) && fval(rc->terrain, SWIM_INTO) && fval(rc->terrain, SEA_REGION)))) { for (sh2 = rc->ships; sh2; sh2 = sh2->next) { diff --git a/src/piracy.test.c b/src/piracy.test.c index 419f73073..005ce5857 100644 --- a/src/piracy.test.c +++ b/src/piracy.test.c @@ -26,7 +26,7 @@ static void setup_piracy(void) { lang = get_or_create_locale("de"); locale_setstring(lang, directions[D_EAST], "OSTEN"); init_directions(lang); - test_create_terrain("ocean", SAIL_INTO | SEA_REGION); + test_create_terrain("ocean", SEA_REGION); st_boat = test_create_shiptype("boat"); st_boat->cargo = 1000; } @@ -184,7 +184,7 @@ static void test_piracy_cmd_land_to_land(CuTest * tc) { test_cleanup(); - setup_pirate(&pirate, 0, 0, "boat", &ord, &victim, SAIL_INTO, "boat"); + setup_pirate(&pirate, 0, 0, "boat", &ord, &victim, SEA_REGION, "boat"); set_level(pirate, SK_SAILING, pirate->ship->type->sumskill); r = pirate->region; diff --git a/src/tests.c b/src/tests.c index a0d54a4cc..fcb34040c 100644 --- a/src/tests.c +++ b/src/tests.c @@ -54,7 +54,7 @@ struct region *test_create_region(int x, int y, const terrain_type *terrain) if (!terrain) { terrain_type *t = get_or_create_terrain("plain"); t->size = 1000; - fset(t, LAND_REGION|CAVALRY_REGION|FOREST_REGION|FLY_INTO|WALK_INTO|SAIL_INTO); + fset(t, LAND_REGION|CAVALRY_REGION|FOREST_REGION|FLY_INTO|WALK_INTO); terraform_region(r, t); } else { @@ -196,9 +196,10 @@ ship_type * test_create_shiptype(const char * name) free(stype->coasts); } stype->coasts = - (terrain_type **)malloc(sizeof(terrain_type *) * 2); - stype->coasts[0] = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | SAIL_INTO | FLY_INTO); - stype->coasts[1] = NULL; + (terrain_type **)malloc(sizeof(terrain_type *) * 3); + stype->coasts[0] = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | FLY_INTO); + stype->coasts[1] = test_create_terrain("ocean", SEA_REGION | SWIM_INTO | FLY_INTO); + stype->coasts[2] = NULL; if (default_locale) { locale_setstring(default_locale, name, name); } @@ -322,10 +323,10 @@ 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 | SAIL_INTO | FLY_INTO); + t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | 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); + t_ocean = test_create_terrain("ocean", SEA_REGION | SWIM_INTO | FLY_INTO); t_ocean->size = 0; island[0] = test_create_region(0, 0, t_plain); @@ -390,6 +391,29 @@ void test_clear_messages(faction *f) { } } +void assert_message(CuTest * tc, message *msg, char *name, int numpar) { + const message_type *mtype = msg->type; + assert(mtype); + + CuAssertStrEquals(tc, name, mtype->name); + CuAssertIntEquals(tc, numpar, mtype->nparameters); +} + +void assert_pointer_parameter(CuTest * tc, message *msg, int index, void *arg) { + const message_type *mtype = (msg)->type; + CuAssertIntEquals((tc), VAR_VOIDPTR, mtype->types[(index)]->vtype);CuAssertPtrEquals((tc), (arg), msg->parameters[(index)].v); +} + +void assert_int_parameter(CuTest * tc, message *msg, int index, int arg) { + const message_type *mtype = (msg)->type; + CuAssertIntEquals((tc), VAR_INT, mtype->types[(index)]->vtype);CuAssertIntEquals((tc), (arg), msg->parameters[(index)].i); +} + +void assert_string_parameter(CuTest * tc, message *msg, int index, const char *arg) { + const message_type *mtype = (msg)->type; + CuAssertIntEquals((tc), VAR_VOIDPTR, mtype->types[(index)]->vtype);CuAssertStrEquals((tc), (arg), msg->parameters[(index)].v); +} + void disabled_test(void *suite, void (*test)(CuTest *), const char *name) { (void)test; fprintf(stderr, "%s: SKIP\n", name); diff --git a/src/tests.h b/src/tests.h index 9836c42ab..d18ff6456 100644 --- a/src/tests.h +++ b/src/tests.h @@ -54,6 +54,11 @@ extern "C" { struct message * test_find_messagetype(struct message_list *msgs, const char *name); struct message * test_get_last_message(struct message_list *mlist); void test_clear_messages(struct faction *f); + void assert_message(struct CuTest * tc, struct message *msg, char *name, int numpar); + + void assert_pointer_parameter(struct CuTest * tc, struct message *msg, int index, void *arg); + void assert_int_parameter(struct CuTest * tc, struct message *msg, int index, int arg); + void assert_string_parameter(struct CuTest * tc, struct message *msg, int index, const char *arg); void disabled_test(void *suite, void (*)(struct CuTest *), const char *name); diff --git a/src/util/log.test.c b/src/util/log.test.c index 2750d4b1e..95cbefa96 100644 --- a/src/util/log.test.c +++ b/src/util/log.test.c @@ -23,10 +23,10 @@ static void test_logging(CuTest * tc) struct log_t * id2 = log_create(LOG_CPWARNING, str2, log_string); CuAssertTrue(tc, id1!=id2); log_warning("Hello %s", "World"); - CuAssertStrEquals(tc, str1, "World"); - CuAssertStrEquals(tc, str2, "World"); log_destroy(id1); log_destroy(id2); + CuAssertStrEquals(tc, "World", str1); + CuAssertStrEquals(tc, "World", str2); } CuSuite *get_log_suite(void) diff --git a/src/util/parser.c b/src/util/parser.c index 63b293e82..741fd573f 100644 --- a/src/util/parser.c +++ b/src/util/parser.c @@ -192,7 +192,7 @@ char *parse_token(const char **str, char *lbuf, size_t buflen) copy = true; } if (copy) { - if (cursor - buflen < lbuf - 1) { + if (cursor - buflen < lbuf - len) { memcpy(cursor, ctoken, len); cursor += len; } diff --git a/src/util/parser.test.c b/src/util/parser.test.c index 95ef70a17..da0d8dacb 100644 --- a/src/util/parser.test.c +++ b/src/util/parser.test.c @@ -1,8 +1,58 @@ #include #include "parser.h" +#include #include +static void test_parse_token(CuTest *tc) { + char lbuf[8]; + const char *tok; + const char *str, *orig; + + orig = str = "SHORT TOKEN"; + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, (void *)(orig+5), (void *)str); + CuAssertStrEquals(tc, "SHORT", tok); + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, (void *)(orig + strlen(orig)), (void *)str); + CuAssertStrEquals(tc, "TOKEN", tok); + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, NULL, (void *)tok); +} + +static void test_parse_token_limit(CuTest *tc) { + char lbuf[8]; + const char *tok; + const char *str, *orig; + + orig = str = "LONG_TOKEN"; + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, (void *)(orig + strlen(orig)), (void *)str); + CuAssertStrEquals(tc, tok, "LONG_TO"); + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, NULL, (void *)tok); +} + +static void test_parse_token_limit_utf8(CuTest *tc) { + char lbuf[8]; + const char *tok; + const char *orig = "a\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"; /* auml ouml uuml szlig, 8 bytes long */ + const char *str = orig+1; + + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, (void *)(orig + strlen(orig)), (void *)str); + CuAssertStrEquals(tc, tok, "\xc3\xa4\xc3\xb6\xc3\xbc"); // just three letters fit, 6 bytes long + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, NULL, (void *)tok); + + str = orig; // now with an extra byte in the front, maxing out lbuf exactly + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, (void *)(orig + strlen(orig)), (void *)str); + CuAssertStrEquals(tc, tok, "a\xc3\xa4\xc3\xb6\xc3\xbc"); + tok = parse_token(&str, lbuf, sizeof(lbuf)); + CuAssertPtrEquals(tc, NULL, (void *)tok); +} + static void test_gettoken(CuTest *tc) { char token[128]; init_tokens_str("HELP ONE TWO THREE"); @@ -64,6 +114,9 @@ CuSuite *get_parser_suite(void) CuSuite *suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_atoip); SUITE_ADD_TEST(suite, test_skip_token); + SUITE_ADD_TEST(suite, test_parse_token); + SUITE_ADD_TEST(suite, test_parse_token_limit); + SUITE_ADD_TEST(suite, test_parse_token_limit_utf8); SUITE_ADD_TEST(suite, test_gettoken); SUITE_ADD_TEST(suite, test_gettoken_short); SUITE_ADD_TEST(suite, test_getintegers);