From 96b7c92d81e6e50962a854783b6c2e151bbbe7ef Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Tue, 31 May 2016 01:48:21 +0200 Subject: [PATCH 01/19] fail fast for wrong message parameters simplifies debugging --- src/kernel/messages.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/kernel/messages.c b/src/kernel/messages.c index 330c2feeb..ecdc588a8 100644 --- a/src/kernel/messages.c +++ b/src/kernel/messages.c @@ -194,6 +194,7 @@ message *msg_message(const char *name, const char *sig, ...) } else { log_error("invalid parameter %s for message type %s\n", paramname, mtype->name); + assert(!"invalid parameter for message type"); } while (*ic && !isalnum(*ic)) ic++; @@ -201,6 +202,7 @@ message *msg_message(const char *name, const char *sig, ...) va_end(vargs); if (argnum != mtype->nparameters) { log_error("not enough parameters for message type %s\n", mtype->name); + assert(!"not enough parameters for message type"); } return msg_create(mtype, args); From ce312afc95caac39420b5173a3429646f6f4f218 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Tue, 31 May 2016 01:49:37 +0200 Subject: [PATCH 02/19] fix dragon growl for regions with an apostrophe --- res/core/messages.xml | 11 ++++++ src/monsters.c | 30 ++++------------ src/monsters.test.c | 84 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 24 deletions(-) diff --git a/res/core/messages.xml b/res/core/messages.xml index 15de4a153..410b673e1 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -8441,4 +8441,15 @@ "$unit($unit) in $region($region): '$order($command)' - Heroes cannot recruit." + + + + + + + + "$unit($dragon): \"$if($eq($choice,0), "Groaaaam... ", "")$if($eq($choice,1), "Chrrrr... ", "")$if($eq($choice,2), "Roooarrr... ", "")$if($eq($choice,3), "Tschrrrk... ", "")$if($eq($choice,4), "Shhhhhh... ", "")$if($eq($number,1), "Ich rieche", "Wir riechen") etwas in $region($target)\"." + "$unit($dragon): \"$if($eq($choice,0), "Groaaaam... ", "")$if($eq($choice,1), "Chrrrr... ", "")$if($eq($choice,2), "Roooarrr... ", "")$if($eq($choice,3), "Tschrrrk... ", "")$if($eq($choice,4), "Shhhhhh... ", "")$if($eq($number,1), "I smell", "We smell") something in $region($target)\"." + + diff --git a/src/monsters.c b/src/monsters.c index 674ab0999..e5f07c2f8 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -547,21 +547,13 @@ 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; + if (rname(target, lang)) { + message *msg = msg_message("dragon_growl", "dragon number target choice", u, u->number, target, rand); + ADDMSG(&u->region->msgs, msg); } - return ""; } extern struct attrib_type at_direction; @@ -707,17 +699,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..41b655cd6 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 @@ -185,11 +188,83 @@ static void test_dragon_attacks_the_rich(CuTest * tc) test_cleanup(); } +typedef struct msg_info { + const char *name; + int numpar; + const char **argv; + const char **pnames, **tnames; + variant *values; +} msg_info; + +static msg_info *test_create_messagetype(const struct locale *lang, const char *name, int num_args, ...) { + va_list vargs; + const message_type *mtype; + char zBuffer[128]; + int i; + msg_info *info = malloc(sizeof(msg_info)); + info->argv = calloc(num_args + 1, sizeof(char *)); + info->pnames = calloc(num_args, sizeof(char *)); + info->tnames = calloc(num_args, sizeof(char *)); + info->values = calloc(num_args, sizeof(variant_type)); + + va_start(vargs, num_args); + + info->numpar = num_args; + info->name = name; + + for(i = 0; ipnames[i] = va_arg(vargs, char *); + info->tnames[i] = va_arg(vargs, char *); + sprintf(zBuffer, "%s:%s", info->pnames[i], info->tnames[i]); + info->argv[i] = _strdup(zBuffer); + } + + mtype = mt_register(mt_new(name, info->argv)); + nrt_register(mtype, lang, "mocktext", 0, "mocksection"); + + for(i = 0; itypes[i]->vtype == VAR_VOIDPTR) { + info->values[i].v = va_arg(vargs, char *); + } else if (mtype->types[i]->vtype == VAR_INT) { + info->values[i].i = va_arg(vargs, int); + } else { + assert(!"unknown variant type"); + } + } + va_end(vargs); + return info; +} + +static void assert_message(CuTest * tc, message *msg, msg_info *info) +{ + const message_type *mtype = msg->type; + assert(mtype); + int i; + + CuAssertStrEquals(tc, info->name, mtype->name); + CuAssertIntEquals(tc, info->numpar, mtype->nparameters); + for (i = 0; i != mtype->nparameters; ++i) { + CuAssertStrEquals(tc, info->pnames[i], mtype->pnames[i]); + CuAssertStrEquals(tc, info->tnames[i], mtype->types[i]->name); + if (mtype->types[i]->vtype == VAR_VOIDPTR) { + CuAssertPtrEquals(tc, info->values[i].v, msg->parameters[i].v); + } else if (mtype->types[i]->vtype == VAR_INT) { + CuAssertIntEquals(tc, info->values[i].i, msg->parameters[i].i); + } else { + assert(!"unknown variant type"); + } + } +} + +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; + struct msg_info *msginfo; create_monsters(&f, &f2, &u, &m); rsetmoney(findregion(1, 0), 1000); @@ -202,6 +277,15 @@ static void test_dragon_moves(CuTest * tc) plan_monsters(f2); CuAssertPtrNotNull(tc, find_order("move east", m)); + + register_reports(); + msginfo = test_create_messagetype(f->locale, "dragon_growl", 4, "dragon", "unit", "number", "int", "target", "region", "choice", "int", m, 1, findregion(1,0), 3); + + random_growl(m, findregion(1, 0), 3); + + msg = test_get_last_message(r->msgs); + assert_message(tc, msg, msginfo); + test_cleanup(); } From cafa29426f5a295a4d131d6f135970f11e9f9513 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Tue, 31 May 2016 10:37:01 +0200 Subject: [PATCH 03/19] simplified message_test --- src/monsters.test.c | 74 ++------------------------------------------- src/tests.c | 23 ++++++++++++++ src/tests.h | 1 + 3 files changed, 26 insertions(+), 72 deletions(-) diff --git a/src/monsters.test.c b/src/monsters.test.c index 41b655cd6..387ac3d5d 100644 --- a/src/monsters.test.c +++ b/src/monsters.test.c @@ -188,74 +188,6 @@ static void test_dragon_attacks_the_rich(CuTest * tc) test_cleanup(); } -typedef struct msg_info { - const char *name; - int numpar; - const char **argv; - const char **pnames, **tnames; - variant *values; -} msg_info; - -static msg_info *test_create_messagetype(const struct locale *lang, const char *name, int num_args, ...) { - va_list vargs; - const message_type *mtype; - char zBuffer[128]; - int i; - msg_info *info = malloc(sizeof(msg_info)); - info->argv = calloc(num_args + 1, sizeof(char *)); - info->pnames = calloc(num_args, sizeof(char *)); - info->tnames = calloc(num_args, sizeof(char *)); - info->values = calloc(num_args, sizeof(variant_type)); - - va_start(vargs, num_args); - - info->numpar = num_args; - info->name = name; - - for(i = 0; ipnames[i] = va_arg(vargs, char *); - info->tnames[i] = va_arg(vargs, char *); - sprintf(zBuffer, "%s:%s", info->pnames[i], info->tnames[i]); - info->argv[i] = _strdup(zBuffer); - } - - mtype = mt_register(mt_new(name, info->argv)); - nrt_register(mtype, lang, "mocktext", 0, "mocksection"); - - for(i = 0; itypes[i]->vtype == VAR_VOIDPTR) { - info->values[i].v = va_arg(vargs, char *); - } else if (mtype->types[i]->vtype == VAR_INT) { - info->values[i].i = va_arg(vargs, int); - } else { - assert(!"unknown variant type"); - } - } - va_end(vargs); - return info; -} - -static void assert_message(CuTest * tc, message *msg, msg_info *info) -{ - const message_type *mtype = msg->type; - assert(mtype); - int i; - - CuAssertStrEquals(tc, info->name, mtype->name); - CuAssertIntEquals(tc, info->numpar, mtype->nparameters); - for (i = 0; i != mtype->nparameters; ++i) { - CuAssertStrEquals(tc, info->pnames[i], mtype->pnames[i]); - CuAssertStrEquals(tc, info->tnames[i], mtype->types[i]->name); - if (mtype->types[i]->vtype == VAR_VOIDPTR) { - CuAssertPtrEquals(tc, info->values[i].v, msg->parameters[i].v); - } else if (mtype->types[i]->vtype == VAR_INT) { - CuAssertIntEquals(tc, info->values[i].i, msg->parameters[i].i); - } else { - assert(!"unknown variant type"); - } - } -} - extern void random_growl(const unit *u, region *tr, int rand); static void test_dragon_moves(CuTest * tc) @@ -264,7 +196,6 @@ static void test_dragon_moves(CuTest * tc) region *r; unit *u, *m; struct message *msg; - struct msg_info *msginfo; create_monsters(&f, &f2, &u, &m); rsetmoney(findregion(1, 0), 1000); @@ -278,13 +209,12 @@ static void test_dragon_moves(CuTest * tc) CuAssertPtrNotNull(tc, find_order("move east", m)); - register_reports(); - msginfo = test_create_messagetype(f->locale, "dragon_growl", 4, "dragon", "unit", "number", "int", "target", "region", "choice", "int", m, 1, findregion(1,0), 3); + mt_register(mt_new_va("dragon_growl", "dragon:unit", "number:int", "target:region", "choice:int", 0)); random_growl(m, findregion(1, 0), 3); msg = test_get_last_message(r->msgs); - assert_message(tc, msg, msginfo); + assert_message(tc, msg, "dragon_growl", 4, m, 1, findregion(1,0), 3); test_cleanup(); } diff --git a/src/tests.c b/src/tests.c index a0d54a4cc..c50065e9c 100644 --- a/src/tests.c +++ b/src/tests.c @@ -390,6 +390,29 @@ void test_clear_messages(faction *f) { } } +void assert_message(CuTest * tc, message *msg, char *name, int numpar, ...) +{ + va_list vargs; + const message_type *mtype = msg->type; + assert(mtype); + int i; + + va_start(vargs, numpar); + + CuAssertStrEquals(tc, name, mtype->name); + CuAssertIntEquals(tc, numpar, mtype->nparameters); + for (i = 0; i != mtype->nparameters; ++i) { + variant value = va_arg(vargs, variant); + if (mtype->types[i]->vtype == VAR_VOIDPTR) { + CuAssertPtrEquals(tc, value.v, msg->parameters[i].v); + } else if (mtype->types[i]->vtype == VAR_INT) { + CuAssertIntEquals(tc, value.i, msg->parameters[i].i); + } else { + assert(!"unknown variant type"); + } + } +} + 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..fafb1755d 100644 --- a/src/tests.h +++ b/src/tests.h @@ -54,6 +54,7 @@ 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 disabled_test(void *suite, void (*)(struct CuTest *), const char *name); From eac94bdf4a011f573a75511c960fdf98554a91ab Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 6 Jun 2016 22:14:28 +0200 Subject: [PATCH 04/19] add a test that demonstrates bug 2211 --- src/laws.test.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/laws.test.c b/src/laws.test.c index 1b2136db7..8363921a9 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -1251,6 +1251,38 @@ static void test_show_without_item(CuTest *tc) test_cleanup(); } +static void test_show_elf(CuTest *tc) { + order *ord; + unit *u; + struct locale *loc; + message * msg; + + test_cleanup(); + + 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(0), 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); + 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 +1418,7 @@ 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_immigration); SUITE_ADD_TEST(suite, test_demon_hunger); From c79ecc1a7840786a2ca5f60c78c960bada69c337 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 6 Jun 2016 23:24:33 +0200 Subject: [PATCH 05/19] fix bug 2211, ignore a match for items we don't have. --- src/laws.c | 142 +++++++++++++++++++++++------------------------- src/laws.test.c | 6 +- 2 files changed, 73 insertions(+), 75 deletions(-) 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 8363921a9..290933369 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -1253,13 +1253,14 @@ static void test_show_without_item(CuTest *tc) static void test_show_elf(CuTest *tc) { order *ord; + race * rc; unit *u; struct locale *loc; message * msg; test_cleanup(); - test_create_race("elf"); + rc = test_create_race("elf"); test_create_itemtype("elvenhorse"); loc = get_or_create_locale("de"); @@ -1272,13 +1273,14 @@ static void test_show_elf(CuTest *tc) { CuAssertPtrNotNull(tc, finditemtype("elf", loc)); CuAssertPtrNotNull(tc, findrace("elf", loc)); - u = test_create_unit(test_create_faction(0), test_create_region(0, 0, 0)); + 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); + test_clear_messages(u->faction); free_order(ord); test_cleanup(); } From 44d8f49bc31f8acf9dceb241aa5bd9e43c7ec0fa Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 6 Jun 2016 23:39:40 +0200 Subject: [PATCH 06/19] test SHOW only works for your own race. testing message contents is hard! --- src/laws.test.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/laws.test.c b/src/laws.test.c index 290933369..7f947be45 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -1260,6 +1260,7 @@ static void test_show_elf(CuTest *tc) { test_cleanup(); + mt_register(mt_new_va("msg_event", "string:string", 0)); rc = test_create_race("elf"); test_create_itemtype("elvenhorse"); @@ -1280,11 +1281,53 @@ static void test_show_elf(CuTest *tc) { 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; } @@ -1421,6 +1464,7 @@ CuSuite *get_laws_suite(void) 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); From 1443c24b36c6258f63b47c12f4b38d8f0c233b7e Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 6 Jun 2016 23:45:11 +0200 Subject: [PATCH 07/19] fix gcc build --- src/laws.test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/laws.test.c b/src/laws.test.c index 7f947be45..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) { From 4414d7ef5718f93be107b4944fa49e9a5de212ca Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Tue, 7 Jun 2016 21:47:09 +0200 Subject: [PATCH 08/19] fix parse_token buffer overrun. missing tests for parse_token. this fixes http://bugs.eressea.de/view.php?id=2206 --- src/util/parser.c | 2 +- src/util/parser.test.c | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) 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..3c60fee54 100644 --- a/src/util/parser.test.c +++ b/src/util/parser.test.c @@ -3,6 +3,55 @@ #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 + 11), (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 + 10), (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 +113,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); From 394621956d2598c4ae398049796997df734bea93 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Tue, 7 Jun 2016 21:51:28 +0200 Subject: [PATCH 09/19] missing include, gcc fix --- src/util/parser.test.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/parser.test.c b/src/util/parser.test.c index 3c60fee54..da0d8dacb 100644 --- a/src/util/parser.test.c +++ b/src/util/parser.test.c @@ -1,6 +1,7 @@ #include #include "parser.h" +#include #include static void test_parse_token(CuTest *tc) { @@ -13,7 +14,7 @@ static void test_parse_token(CuTest *tc) { CuAssertPtrEquals(tc, (void *)(orig+5), (void *)str); CuAssertStrEquals(tc, "SHORT", tok); tok = parse_token(&str, lbuf, sizeof(lbuf)); - CuAssertPtrEquals(tc, (void *)(orig + 11), (void *)str); + CuAssertPtrEquals(tc, (void *)(orig + strlen(orig)), (void *)str); CuAssertStrEquals(tc, "TOKEN", tok); tok = parse_token(&str, lbuf, sizeof(lbuf)); CuAssertPtrEquals(tc, NULL, (void *)tok); @@ -26,7 +27,7 @@ static void test_parse_token_limit(CuTest *tc) { orig = str = "LONG_TOKEN"; tok = parse_token(&str, lbuf, sizeof(lbuf)); - CuAssertPtrEquals(tc, (void *)(orig + 10), (void *)str); + 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); From 546ffe87b83ad0ea450bbe567c6b41e6515c68f9 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 10 Jun 2016 18:00:22 +0200 Subject: [PATCH 10/19] change ship_allowed constants just a little bit --- src/modules/score.c | 2 +- src/move.c | 2 +- src/move.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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/move.c b/src/move.c index e602b073c..98ed138fb 100644 --- a/src/move.c +++ b/src/move.c @@ -836,7 +836,7 @@ static void drifting_ships(region * r) 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) { + if (rn != NULL && fval(rn->terrain, SAIL_INTO) && check_ship_allowed(sh, rn) >= 0) { rnext = rn; if (!fval(rnext->terrain, SEA_REGION)) break; diff --git a/src/move.h b/src/move.h index 0417dcec5..bdb4f61c7 100644 --- a/src/move.h +++ b/src/move.h @@ -77,8 +77,8 @@ 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 From f08957b513e4957857a1e03cd2e4f327a4a43b86 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 10 Jun 2016 18:24:18 +0200 Subject: [PATCH 11/19] test target selection rules for drifting ships. --- src/move.c | 32 +++++++++++++++++++------------- src/move.h | 1 + src/move.test.c | 19 +++++++++++++++++++ src/tests.c | 5 +++-- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/move.c b/src/move.c index 98ed138fb..34490b9f6 100644 --- a/src/move.c +++ b/src/move.c @@ -783,9 +783,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 && fval(rn->terrain, SAIL_INTO) && 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 +813,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 +847,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) { diff --git a/src/move.h b/src/move.h index bdb4f61c7..9c5893629 100644 --- a/src/move.h +++ b/src/move.h @@ -83,6 +83,7 @@ extern "C" { #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..df12778f3 100644 --- a/src/move.test.c +++ b/src/move.test.c @@ -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|SAIL_INTO); + t_plain = test_create_terrain("plain", LAND_REGION|SAIL_INTO); + 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/tests.c b/src/tests.c index a0d54a4cc..25e82d578 100644 --- a/src/tests.c +++ b/src/tests.c @@ -196,9 +196,10 @@ ship_type * test_create_shiptype(const char * name) free(stype->coasts); } stype->coasts = - (terrain_type **)malloc(sizeof(terrain_type *) * 2); + (terrain_type **)malloc(sizeof(terrain_type *) * 3); stype->coasts[0] = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | SAIL_INTO | FLY_INTO); - stype->coasts[1] = NULL; + stype->coasts[1] = test_create_terrain("ocean", SEA_REGION | SWIM_INTO | SAIL_INTO | FLY_INTO); + stype->coasts[2] = NULL; if (default_locale) { locale_setstring(default_locale, name, name); } From 2b17656efe92c142e5c6f53216d9b85c4038969c Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 10 Jun 2016 20:45:23 +0200 Subject: [PATCH 12/19] custom message for insects that cannot enter a region. --- src/move.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/move.c b/src/move.c index 34490b9f6..671b6aa20 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; } } @@ -1952,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 { From 7fd1d3d616502aab1a2baab7ef1d8491f7cc4e1b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 10 Jun 2016 20:50:22 +0200 Subject: [PATCH 13/19] remove ocean from coasts, SEA_REGION is enough --- res/core/ships.xml | 7 ------- res/e3a/ships.xml | 12 ------------ res/ships/boat.xml | 1 - 3 files changed, 20 deletions(-) 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 @@ - From 78fa6d3a47a813193b7dc1fe9f4f731652de4038 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 10 Jun 2016 20:55:27 +0200 Subject: [PATCH 14/19] remove the SAIL_INTO flag --- src/kernel/jsonconf.test.c | 4 ++-- src/kernel/terrain.h | 1 - src/monsters.test.c | 2 +- src/move.c | 2 +- src/move.test.c | 14 +++++++------- src/piracy.c | 2 +- src/piracy.test.c | 4 ++-- src/tests.c | 10 +++++----- 8 files changed, 19 insertions(+), 20 deletions(-) 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/monsters.test.c b/src/monsters.test.c index a7a4d9b18..1660800fe 100644 --- a/src/monsters.test.c +++ b/src/monsters.test.c @@ -55,7 +55,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); diff --git a/src/move.c b/src/move.c index 671b6aa20..e7ca523b5 100644 --- a/src/move.c +++ b/src/move.c @@ -784,7 +784,7 @@ region * drift_target(ship *sh) { region *rn; direction_t dir = (direction_t)((d + d_offset) % MAXDIRECTIONS); rn = rconnect(sh->region, dir); - if (rn != NULL && fval(rn->terrain, SAIL_INTO) && check_ship_allowed(sh, rn) >= 0) { + if (rn != NULL && check_ship_allowed(sh, rn) >= 0) { rnext = rn; if (!fval(rnext->terrain, SEA_REGION)) { // prefer drifting towards non-ocean regions diff --git a/src/move.test.c b/src/move.test.c index df12778f3..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); @@ -504,8 +504,8 @@ static void test_drifting_ships(CuTest *tc) { terrain_type *t_ocean, *t_plain; ship_type *st_boat; test_cleanup(); - t_ocean = test_create_terrain("ocean", SEA_REGION|SAIL_INTO); - t_plain = test_create_terrain("plain", LAND_REGION|SAIL_INTO); + 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"); 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 25e82d578..4e6d98a74 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 { @@ -197,8 +197,8 @@ ship_type * test_create_shiptype(const char * name) } stype->coasts = (terrain_type **)malloc(sizeof(terrain_type *) * 3); - stype->coasts[0] = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION | SAIL_INTO | FLY_INTO); - stype->coasts[1] = test_create_terrain("ocean", SEA_REGION | SWIM_INTO | SAIL_INTO | FLY_INTO); + 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); @@ -323,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); From c5014fd76691f792bcd0d169ad4321e86a0609ca Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Sat, 11 Jun 2016 13:47:38 +0200 Subject: [PATCH 15/19] integrated Ennos suggestions for dragon growls Dragons now growl 20% more elegantly! --- res/core/de/strings.xml | 18 ++++++++++++++++++ res/core/messages.xml | 6 +++--- src/kernel/messages.c | 2 -- src/monsters.c | 12 +++++++++++- src/monsters.test.c | 8 ++++++-- src/tests.c | 32 ++++++++++++++++---------------- src/tests.h | 6 +++++- src/util/log.test.c | 4 ++-- 8 files changed, 61 insertions(+), 27 deletions(-) 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 410b673e1..99cf4d145 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -8446,10 +8446,10 @@ - + - "$unit($dragon): \"$if($eq($choice,0), "Groaaaam... ", "")$if($eq($choice,1), "Chrrrr... ", "")$if($eq($choice,2), "Roooarrr... ", "")$if($eq($choice,3), "Tschrrrk... ", "")$if($eq($choice,4), "Shhhhhh... ", "")$if($eq($number,1), "Ich rieche", "Wir riechen") etwas in $region($target)\"." - "$unit($dragon): \"$if($eq($choice,0), "Groaaaam... ", "")$if($eq($choice,1), "Chrrrr... ", "")$if($eq($choice,2), "Roooarrr... ", "")$if($eq($choice,3), "Tschrrrk... ", "")$if($eq($choice,4), "Shhhhhh... ", "")$if($eq($number,1), "I smell", "We smell") something in $region($target)\"." + "$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/src/kernel/messages.c b/src/kernel/messages.c index ecdc588a8..330c2feeb 100644 --- a/src/kernel/messages.c +++ b/src/kernel/messages.c @@ -194,7 +194,6 @@ message *msg_message(const char *name, const char *sig, ...) } else { log_error("invalid parameter %s for message type %s\n", paramname, mtype->name); - assert(!"invalid parameter for message type"); } while (*ic && !isalnum(*ic)) ic++; @@ -202,7 +201,6 @@ message *msg_message(const char *name, const char *sig, ...) va_end(vargs); if (argnum != mtype->nparameters) { log_error("not enough parameters for message type %s\n", mtype->name); - assert(!"not enough parameters for message type"); } return msg_create(mtype, args); diff --git a/src/monsters.c b/src/monsters.c index e5f07c2f8..2fc2fcde3 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -550,8 +550,18 @@ static order *monster_seeks_target(region * r, unit * u) void random_growl(const unit *u, region *target, int rand) { 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 choice", u, u->number, target, rand); + message *msg = msg_message("dragon_growl", "dragon number target growl", u, u->number, target, growl); ADDMSG(&u->region->msgs, msg); } } diff --git a/src/monsters.test.c b/src/monsters.test.c index 387ac3d5d..06c434940 100644 --- a/src/monsters.test.c +++ b/src/monsters.test.c @@ -209,12 +209,16 @@ static void test_dragon_moves(CuTest * tc) CuAssertPtrNotNull(tc, find_order("move east", m)); - mt_register(mt_new_va("dragon_growl", "dragon:unit", "number:int", "target:region", "choice:int", 0)); + 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, m, 1, findregion(1,0), 3); + 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/tests.c b/src/tests.c index c50065e9c..5b8c2a945 100644 --- a/src/tests.c +++ b/src/tests.c @@ -390,27 +390,27 @@ void test_clear_messages(faction *f) { } } -void assert_message(CuTest * tc, message *msg, char *name, int numpar, ...) -{ - va_list vargs; +void assert_message(CuTest * tc, message *msg, char *name, int numpar) { const message_type *mtype = msg->type; assert(mtype); - int i; - - va_start(vargs, numpar); CuAssertStrEquals(tc, name, mtype->name); CuAssertIntEquals(tc, numpar, mtype->nparameters); - for (i = 0; i != mtype->nparameters; ++i) { - variant value = va_arg(vargs, variant); - if (mtype->types[i]->vtype == VAR_VOIDPTR) { - CuAssertPtrEquals(tc, value.v, msg->parameters[i].v); - } else if (mtype->types[i]->vtype == VAR_INT) { - CuAssertIntEquals(tc, value.i, msg->parameters[i].i); - } else { - assert(!"unknown variant type"); - } - } +} + +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) { diff --git a/src/tests.h b/src/tests.h index fafb1755d..d18ff6456 100644 --- a/src/tests.h +++ b/src/tests.h @@ -54,7 +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_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) From 61d93b7931894401511eadc5d4ad2294e9d04ce9 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Sun, 12 Jun 2016 21:54:00 +0200 Subject: [PATCH 16/19] fix two bugs concerning horse bonus switched E3 horse damage and attack bonuses horses help fleeing if when riding is 0 --- src/battle.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/battle.c b/src/battle.c index 231d7f2e1..88dc89ff6 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; @@ -2384,7 +2384,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 +2392,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 +2408,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 +2422,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; From afdd20c4dd3ba5bd13a642666019c63bf3c06888 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Sun, 12 Jun 2016 21:54:42 +0200 Subject: [PATCH 17/19] healing potion works once per battle with 100% --- res/core/messages.xml | 4 ++-- src/battle.c | 33 +++++++++------------------------ src/battle.h | 1 + 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/res/core/messages.xml b/res/core/messages.xml index 99cf4d145..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." diff --git a/src/battle.c b/src/battle.c index 88dc89ff6..003fffdbd 100644 --- a/src/battle.c +++ b/src/battle.c @@ -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,38 +1313,24 @@ 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) { - 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; - return false; - } + if (oldpotiontype[P_HEAL] && i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0 && !fval(&df->person[dt.index], FL_HEALING_USED)) { + i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); + 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; + return false; } ++at.fighter->kills; 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; From e6907f570e7b5e669e6bc3ee5a9f97409ee4526f Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Mon, 13 Jun 2016 09:25:08 +0200 Subject: [PATCH 18/19] use up old potion effects --- src/battle.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/battle.c b/src/battle.c index 003fffdbd..422271bbc 100644 --- a/src/battle.c +++ b/src/battle.c @@ -1322,15 +1322,19 @@ terminate(troop dt, troop at, int type, const char *damage, bool missile) return false; } - /* Heiltrank schluerfen und hoffen */ - if (oldpotiontype[P_HEAL] && i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0 && !fval(&df->person[dt.index], FL_HEALING_USED)) { - i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); - 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; - return false; + if (oldpotiontype[P_HEAL] && !fval(&df->person[dt.index], FL_HEALING_USED)) { + if ((get_effect(du, oldpotiontype[P_HEAL]) || i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0)) { + if (get_effect(du, oldpotiontype[P_HEAL])) + change_effect(du, oldpotiontype[P_HEAL], -1); + else + i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); + 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; + } } ++at.fighter->kills; From 40dbe2b0cb108ac3efce74406d8ea11f5b6f0aef Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 17 Jun 2016 21:49:31 +0200 Subject: [PATCH 19/19] delete old healing effects on read remove obsolete effect-handling from battle.c --- src/alchemy.c | 4 ++++ src/battle.c | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) 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 422271bbc..217f73c34 100644 --- a/src/battle.c +++ b/src/battle.c @@ -1323,11 +1323,8 @@ terminate(troop dt, troop at, int type, const char *damage, bool missile) } if (oldpotiontype[P_HEAL] && !fval(&df->person[dt.index], FL_HEALING_USED)) { - if ((get_effect(du, oldpotiontype[P_HEAL]) || i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0)) { - if (get_effect(du, oldpotiontype[P_HEAL])) - change_effect(du, oldpotiontype[P_HEAL], -1); - else - i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); + if (i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0) { + i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); message *m = msg_message("battle::potionsave", "unit", du); message_faction(b, du->faction, m); msg_release(m);