diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71c397f5b..42a65d3be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -68,6 +68,7 @@ set (ERESSEA_SRC battle.c alchemy.c stealth.c + upkeep.c vortex.c names.c reports.c @@ -159,15 +160,16 @@ set(TESTS_SRC tests.test.c reports.test.c stealth.test.c - move.test.c callback.test.c direction.test.c - keyword.test.c - skill.test.c - json.test.c economy.test.c - market.test.c + json.test.c + keyword.test.c laws.test.c + market.test.c + move.test.c + skill.test.c + upkeep.test.c ${UTIL_TESTS} ${KERNEL_TESTS} ${ERESSEA_SRC} diff --git a/src/bind_faction.c b/src/bind_faction.c index d99d5c85f..b62c004f8 100644 --- a/src/bind_faction.c +++ b/src/bind_faction.c @@ -11,11 +11,13 @@ without prior permission by the authors of Eressea. */ #include +#include #include "bind_faction.h" #include "bind_unit.h" #include "bindings.h" #include +#include #include #include #include @@ -452,8 +454,8 @@ static int tolua_faction_get_alliance(lua_State * L) static int tolua_faction_set_alliance(lua_State * L) { - faction *self = (faction *) tolua_tousertype(L, 1, 0); - alliance *alli = (alliance *) tolua_tousertype(L, 2, 0); + struct faction *self = (struct faction *)tolua_tousertype(L, 1, 0); + struct alliance *alli = (struct alliance *) tolua_tousertype(L, 2, 0); setalliance(self, alli); diff --git a/src/economy.c b/src/economy.c index 49f4153a6..36ff16c75 100644 --- a/src/economy.c +++ b/src/economy.c @@ -2154,6 +2154,13 @@ static void buy(unit * u, request ** buyorders, struct order *ord) } /* ------------------------------------------------------------- */ +static void add_income(unit * u, int type, int want, int qty) +{ + if (want == INT_MAX) + want = qty; + ADDMSG(&u->faction->msgs, msg_message("income", + "unit region mode wanted amount", u, u->region, type, want, qty)); +} /* Steuersätze in % bei Burggröße */ static int tax_per_size[7] = { 0, 6, 12, 18, 24, 30, 36 }; diff --git a/src/kernel/config.c b/src/kernel/config.c index 10ab406b9..6cd0b3725 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -2240,27 +2240,6 @@ int besieged(const unit * u) && u->building->besieged >= u->building->size * SIEGEFACTOR); } -int lifestyle(const unit * u) -{ - int need; - plane *pl; - static int gamecookie = -1; - if (gamecookie != global.cookie) { - gamecookie = global.cookie; - } - - if (is_monsters(u->faction)) - return 0; - - need = maintenance_cost(u); - - pl = rplane(u->region); - if (pl && fval(pl, PFL_NOFEED)) - return 0; - - return need; -} - bool has_horses(const struct unit * u) { item *itm = u->items; @@ -2271,56 +2250,6 @@ bool has_horses(const struct unit * u) return false; } -bool hunger(int number, unit * u) -{ - region *r = u->region; - int dead = 0, hpsub = 0; - int hp = u->hp / u->number; - static const char *damage = 0; - static const char *rcdamage = 0; - static const race *rc = 0; - - if (!damage) { - damage = get_param(global.parameters, "hunger.damage"); - if (damage == NULL) - damage = "1d12+12"; - } - if (rc != u_race(u)) { - rcdamage = get_param(u_race(u)->parameters, "hunger.damage"); - rc = u_race(u); - } - - while (number--) { - int dam = dice_rand(rcdamage ? rcdamage : damage); - if (dam >= hp) { - ++dead; - } - else { - hpsub += dam; - } - } - - if (dead) { - /* Gestorbene aus der Einheit nehmen, - * Sie bekommen keine Beerdingung. */ - ADDMSG(&u->faction->msgs, msg_message("starvation", - "unit region dead live", u, r, dead, u->number - dead)); - - scale_number(u, u->number - dead); - deathcounts(r, dead); - } - if (hpsub > 0) { - /* Jetzt die Schäden der nicht gestorbenen abziehen. */ - u->hp -= hpsub; - /* Meldung nur, wenn noch keine für Tote generiert. */ - if (dead == 0) { - /* Durch unzureichende Ernährung wird %s geschwächt */ - ADDMSG(&u->faction->msgs, msg_message("malnourish", "unit region", u, r)); - } - } - return (dead || hpsub); -} - void plagues(region * r, bool ismagic) { int peasants; @@ -2653,51 +2582,6 @@ int maintenance_cost(const struct unit *u) return u_race(u)->maintenance * u->number; } -message *movement_error(unit * u, const char *token, order * ord, - int error_code) -{ - direction_t d; - switch (error_code) { - case E_MOVE_BLOCKED: - d = get_direction(token, u->faction->locale); - return msg_message("moveblocked", "unit direction", u, d); - case E_MOVE_NOREGION: - return msg_feedback(u, ord, "unknowndirection", "dirname", token); - } - return NULL; -} - -bool move_blocked(const unit * u, const region * r, const region * r2) -{ - connection *b; - curse *c; - static const curse_type *fogtrap_ct = NULL; - - if (r2 == NULL) - return true; - b = get_borders(r, r2); - while (b) { - if (b->type->block && b->type->block(b, u, r)) - return true; - b = b->next; - } - - if (fogtrap_ct == NULL) - fogtrap_ct = ct_find("fogtrap"); - c = get_curse(r->attribs, fogtrap_ct); - if (curse_active(c)) - return true; - return false; -} - -void add_income(unit * u, int type, int want, int qty) -{ - if (want == INT_MAX) - want = qty; - ADDMSG(&u->faction->msgs, msg_message("income", - "unit region mode wanted amount", u, u->region, type, want, qty)); -} - int produceexp(struct unit *u, skill_t sk, int n) { if (global.producexpchance > 0.0F) { diff --git a/src/kernel/config.h b/src/kernel/config.h index 02cf6e8f0..9b0f7135b 100644 --- a/src/kernel/config.h +++ b/src/kernel/config.h @@ -324,8 +324,6 @@ extern "C" { */ unsigned int guard_flags(const struct unit *u); - bool hunger(int number, struct unit *u); - int lifestyle(const struct unit *); int besieged(const struct unit *u); int maxworkingpeasants(const struct region *r); bool has_horses(const struct unit *u); @@ -333,11 +331,6 @@ extern "C" { int wage(const struct region *r, const struct faction *f, const struct race *rc, int in_turn); int maintenance_cost(const struct unit *u); - struct message *movement_error(struct unit *u, const char *token, - struct order *ord, int error_code); - bool move_blocked(const struct unit *u, const struct region *src, - const struct region *dest); - void add_income(struct unit *u, int type, int want, int qty); const char *datapath(void); void set_datapath(const char *path); diff --git a/src/kernel/plane.c b/src/kernel/plane.c index d02bfa22c..595ccbf2e 100644 --- a/src/kernel/plane.c +++ b/src/kernel/plane.c @@ -277,7 +277,7 @@ rel_to_abs(const struct plane *pl, const struct faction *f, int rel, return (rel + ursprung_y(f, pl, NULL) + plane_center_y(pl)); } -int resolve_plane(variant id, void *addr) +static int resolve_plane(variant id, void *addr) { int result = 0; plane *pl = NULL; diff --git a/src/kernel/plane.h b/src/kernel/plane.h index 98b897f52..1c7e505dc 100644 --- a/src/kernel/plane.h +++ b/src/kernel/plane.h @@ -74,7 +74,6 @@ extern "C" { extern int rel_to_abs(const struct plane *pl, const struct faction *f, int rel, unsigned char index); extern bool is_watcher(const struct plane *p, const struct faction *f); - extern int resolve_plane(variant data, void *addr); extern void write_plane_reference(const plane * p, struct storage *store); extern int read_plane_reference(plane ** pp, struct storage *store); extern int plane_width(const plane * pl); diff --git a/src/keyword.test.c b/src/keyword.test.c index a3a92a2fa..f2cde5ab1 100644 --- a/src/keyword.test.c +++ b/src/keyword.test.c @@ -1,9 +1,11 @@ #include #include "kernel/types.h" +#include "kernel/config.h" #include "keyword.h" #include "util/language.h" #include "tests.h" +#include #include static void test_init_keywords(CuTest *tc) { @@ -51,6 +53,24 @@ static void test_get_keyword_default(CuTest *tc) { CuAssertIntEquals(tc, K_STUDY, get_keyword("study", lang)); } +static void test_get_shortest_match(CuTest *tc) { + struct locale *lang; + critbit_tree ** cb; + + test_cleanup(); + lang = get_or_create_locale("en"); + + cb = (critbit_tree **)get_translations(lang, UT_KEYWORDS); + /* note that the english order is FIGHT, not COMBAT, so this is a poor example */ + add_translation(cb, "COMBAT", K_STATUS); + add_translation(cb, "COMBATSPELL", K_COMBATSPELL); + + CuAssertIntEquals(tc, NOKEYWORD, get_keyword("", lang)); + CuAssertIntEquals(tc, K_STATUS, get_keyword("COM", lang)); + CuAssertIntEquals(tc, K_STATUS, get_keyword("COMBAT", lang)); + CuAssertIntEquals(tc, K_COMBATSPELL, get_keyword("COMBATS", lang)); +} + #define SUITE_DISABLE_TEST(suite, test) (void)test CuSuite *get_keyword_suite(void) @@ -59,6 +79,7 @@ CuSuite *get_keyword_suite(void) SUITE_ADD_TEST(suite, test_init_keyword); SUITE_ADD_TEST(suite, test_init_keywords); SUITE_ADD_TEST(suite, test_findkeyword); + SUITE_ADD_TEST(suite, test_get_shortest_match); SUITE_DISABLE_TEST(suite, test_get_keyword_default); return suite; } diff --git a/src/laws.c b/src/laws.c index 406c37c77..382aaa72d 100755 --- a/src/laws.c +++ b/src/laws.c @@ -138,229 +138,6 @@ static void checkorders(void) ADDMSG(&f->msgs, msg_message("turnreminder", "")); } -static bool help_money(const unit * u) -{ - if (u_race(u)->ec_flags & GIVEITEM) - return true; - return false; -} - -static void help_feed(unit * donor, unit * u, int *need_p) -{ - int need = *need_p; - int give = get_money(donor) - lifestyle(donor); - give = _min(need, give); - - if (give > 0) { - change_money(donor, -give); - change_money(u, give); - need -= give; - add_spende(donor->faction, u->faction, give, donor->region); - } - *need_p = need; -} - -enum { - FOOD_FROM_PEASANTS = 1, - FOOD_FROM_OWNER = 2, - FOOD_IS_FREE = 4 -}; - -void get_food(region * r) -{ - plane *pl = rplane(r); - unit *u; - int peasantfood = rpeasants(r) * 10; - static int food_rules = -1; - static int gamecookie = -1; - - if (food_rules < 0 || gamecookie != global.cookie) { - gamecookie = global.cookie; - food_rules = get_param_int(global.parameters, "rules.economy.food", 0); - } - - if (food_rules & FOOD_IS_FREE) { - return; - } - /* 1. Versorgung von eigenen Einheiten. Das vorhandene Silber - * wird zunächst so auf die Einheiten aufgeteilt, dass idealerweise - * jede Einheit genug Silber für ihren Unterhalt hat. */ - - for (u = r->units; u; u = u->next) { - int need = lifestyle(u); - - /* Erstmal zurücksetzen */ - freset(u, UFL_HUNGER); - - if (u->ship && (u->ship->flags & SF_FISHING)) { - unit *v; - int c = 2; - for (v = u; c > 0 && v; v = v->next) { - if (v->ship == u->ship) { - int get = 0; - if (v->number <= c) { - get = lifestyle(v); - } - else { - get = lifestyle(v) * c / v->number; - } - if (get) { - change_money(v, get); - } - } - c -= v->number; - } - u->ship->flags -= SF_FISHING; - } - - if (food_rules & FOOD_FROM_PEASANTS) { - faction *owner = region_get_owner(r); - /* if the region is owned, and the owner is nice, then we'll get - * food from the peasants - should not be used with WORK */ - if (owner != NULL && (get_alliance(owner, u->faction) & HELP_MONEY)) { - int rm = rmoney(r); - int use = _min(rm, need); - rsetmoney(r, rm - use); - need -= use; - } - } - - need -= get_money(u); - if (need > 0) { - unit *v; - - for (v = r->units; need && v; v = v->next) { - if (v->faction == u->faction && help_money(v)) { - int give = get_money(v) - lifestyle(v); - give = _min(need, give); - if (give > 0) { - change_money(v, -give); - change_money(u, give); - need -= give; - } - } - } - } - } - - /* 2. Versorgung durch Fremde. Das Silber alliierter Einheiten wird - * entsprechend verteilt. */ - for (u = r->units; u; u = u->next) { - int need = lifestyle(u); - faction *f = u->faction; - - need -= _max(0, get_money(u)); - - if (need > 0) { - unit *v; - - if (food_rules & FOOD_FROM_OWNER) { - /* the owner of the region is the first faction to help out when you're hungry */ - faction *owner = region_get_owner(r); - if (owner && owner != u->faction) { - for (v = r->units; v; v = v->next) { - if (v->faction == owner && alliedunit(v, f, HELP_MONEY) - && help_money(v)) { - help_feed(v, u, &need); - break; - } - } - } - } - for (v = r->units; need && v; v = v->next) { - if (v->faction != f && alliedunit(v, f, HELP_MONEY) - && help_money(v)) { - help_feed(v, u, &need); - } - } - - /* Die Einheit hat nicht genug Geld zusammengekratzt und - * nimmt Schaden: */ - if (need > 0) { - int lspp = lifestyle(u) / u->number; - if (lspp > 0) { - int number = (need + lspp - 1) / lspp; - if (hunger(number, u)) - fset(u, UFL_HUNGER); - } - } - } - } - - /* 3. bestimmen, wie viele Bauern gefressen werden. - * bei fehlenden Bauern den Dämon hungern lassen - */ - for (u = r->units; u; u = u->next) { - if (u_race(u) == get_race(RC_DAEMON)) { - int hungry = u->number; - - /* use peasantblood before eating the peasants themselves */ - const struct potion_type *pt_blood = 0; - const resource_type *rt_blood = rt_find("peasantblood"); - if (rt_blood) { - pt_blood = rt_blood->ptype; - } - if (pt_blood) { - /* always start with the unit itself, then the first known unit that may have some blood */ - unit *donor = u; - while (donor != NULL && hungry > 0) { - int blut = get_effect(donor, pt_blood); - blut = _min(blut, hungry); - if (blut) { - change_effect(donor, pt_blood, -blut); - hungry -= blut; - } - if (donor == u) - donor = r->units; - while (donor != NULL) { - if (u_race(donor) == get_race(RC_DAEMON) && donor != u) { - if (get_effect(donor, pt_blood)) { - /* if he's in our faction, drain him: */ - if (donor->faction == u->faction) - break; - } - } - donor = donor->next; - } - } - } - /* remaining demons feed on peasants */ - if (pl == NULL || !fval(pl, PFL_NOFEED)) { - if (peasantfood >= hungry) { - peasantfood -= hungry; - hungry = 0; - } - else { - hungry -= peasantfood; - peasantfood = 0; - } - if (hungry > 0) { - static int demon_hunger = -1; - if (demon_hunger < 0) { - demon_hunger = get_param_int(global.parameters, "hunger.demons", 0); - } - if (demon_hunger == 0) { - /* demons who don't feed are hungry */ - if (hunger(hungry, u)) - fset(u, UFL_HUNGER); - } - else { - /* no damage, but set the hungry-flag */ - fset(u, UFL_HUNGER); - } - } - } - } - } - rsetpeasants(r, peasantfood / 10); - - /* 3. Von den überlebenden das Geld abziehen: */ - for (u = r->units; u; u = u->next) { - int need = _min(get_money(u), lifestyle(u)); - change_money(u, -need); - } -} - static void age_unit(region * r, unit * u) { if (u_race(u) == get_race(RC_SPELL)) { diff --git a/src/move.c b/src/move.c index bf87d6fda..a174a2872 100644 --- a/src/move.c +++ b/src/move.c @@ -1069,6 +1069,29 @@ unit *is_guarded(region * r, unit * u, unsigned int mask) return NULL; } +bool move_blocked(const unit * u, const region * r, const region * r2) +{ + connection *b; + curse *c; + static const curse_type *fogtrap_ct = NULL; + + if (r2 == NULL) + return true; + b = get_borders(r, r2); + while (b) { + if (b->type->block && b->type->block(b, u, r)) + return true; + b = b->next; + } + + if (fogtrap_ct == NULL) + fogtrap_ct = ct_find("fogtrap"); + c = get_curse(r->attribs, fogtrap_ct); + if (curse_active(c)) + return true; + return false; +} + int movewhere(const unit * u, const char *token, region * r, region ** resultp) { region *r2; @@ -1389,6 +1412,20 @@ static const region_list *reroute(unit * u, const region_list * route, return route; } +static message *movement_error(unit * u, const char *token, order * ord, + int error_code) +{ + direction_t d; + switch (error_code) { + case E_MOVE_BLOCKED: + d = get_direction(token, u->faction->locale); + return msg_message("moveblocked", "unit direction", u, d); + case E_MOVE_NOREGION: + return msg_feedback(u, ord, "unknowndirection", "dirname", token); + } + return NULL; +} + static void make_route(unit * u, order * ord, region_list ** routep) { region_list **iroute = routep; diff --git a/src/move.h b/src/move.h index dd158d224..4b0a9e4c7 100644 --- a/src/move.h +++ b/src/move.h @@ -79,6 +79,8 @@ extern "C" { const struct building_type *bt, bool working); struct unit *owner_buildingtyp(const struct region *r, const struct building_type *bt); + bool move_blocked(const struct unit *u, const struct region *src, + const struct region *dest); #define SA_HARBOUR 2 #define SA_COAST 1 diff --git a/src/report.c b/src/report.c index 0de33ebec..4856390a9 100644 --- a/src/report.c +++ b/src/report.c @@ -32,12 +32,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include /* gamecode includes */ -#include "creport.h" -#include "economy.h" -#include "monster.h" -#include "laws.h" -#include "move.h" #include "alchemy.h" +#include "economy.h" +#include "move.h" +#include "upkeep.h" #include "vortex.h" /* kernel includes */ diff --git a/src/test_eressea.c b/src/test_eressea.c index fb78486d4..a721141d6 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -58,6 +58,7 @@ int RunAllTests(void) ADD_TESTS(suite, market); ADD_TESTS(suite, move); ADD_TESTS(suite, stealth); + ADD_TESTS(suite, upkeep); ADD_TESTS(suite, vortex); CuSuiteRun(suite); diff --git a/src/upkeep.c b/src/upkeep.c new file mode 100644 index 000000000..d105baed0 --- /dev/null +++ b/src/upkeep.c @@ -0,0 +1,308 @@ +#include +#include "upkeep.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alchemy.h" +#include "economy.h" + +#include + +int lifestyle(const unit * u) +{ + int need; + plane *pl; + static int gamecookie = -1; + if (gamecookie != global.cookie) { + gamecookie = global.cookie; + } + + if (is_monsters(u->faction)) + return 0; + + need = maintenance_cost(u); + + pl = rplane(u->region); + if (pl && fval(pl, PFL_NOFEED)) + return 0; + + return need; +} + +static bool help_money(const unit * u) +{ + if (u_race(u)->ec_flags & GIVEITEM) + return true; + return false; +} + +static void help_feed(unit * donor, unit * u, int *need_p) +{ + int need = *need_p; + int give = get_money(donor) - lifestyle(donor); + give = _min(need, give); + + if (give > 0) { + change_money(donor, -give); + change_money(u, give); + need -= give; + add_spende(donor->faction, u->faction, give, donor->region); + } + *need_p = need; +} + +static bool hunger(int number, unit * u) +{ + region *r = u->region; + int dead = 0, hpsub = 0; + int hp = u->hp / u->number; + static const char *damage = 0; + static const char *rcdamage = 0; + static const race *rc = 0; + + if (!damage) { + damage = get_param(global.parameters, "hunger.damage"); + if (damage == NULL) + damage = "1d12+12"; + } + if (rc != u_race(u)) { + rcdamage = get_param(u_race(u)->parameters, "hunger.damage"); + rc = u_race(u); + } + + while (number--) { + int dam = dice_rand(rcdamage ? rcdamage : damage); + if (dam >= hp) { + ++dead; + } + else { + hpsub += dam; + } + } + + if (dead) { + /* Gestorbene aus der Einheit nehmen, + * Sie bekommen keine Beerdingung. */ + ADDMSG(&u->faction->msgs, msg_message("starvation", + "unit region dead live", u, r, dead, u->number - dead)); + + scale_number(u, u->number - dead); + deathcounts(r, dead); + } + if (hpsub > 0) { + /* Jetzt die Schäden der nicht gestorbenen abziehen. */ + u->hp -= hpsub; + /* Meldung nur, wenn noch keine für Tote generiert. */ + if (dead == 0) { + /* Durch unzureichende Ernährung wird %s geschwächt */ + ADDMSG(&u->faction->msgs, msg_message("malnourish", "unit region", u, r)); + } + } + return (dead || hpsub); +} + +void get_food(region * r) +{ + plane *pl = rplane(r); + unit *u; + int peasantfood = rpeasants(r) * 10; + static int food_rules = -1; + static int gamecookie = -1; + + if (food_rules < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + food_rules = get_param_int(global.parameters, "rules.economy.food", 0); + } + + if (food_rules & FOOD_IS_FREE) { + return; + } + /* 1. Versorgung von eigenen Einheiten. Das vorhandene Silber + * wird zunächst so auf die Einheiten aufgeteilt, dass idealerweise + * jede Einheit genug Silber für ihren Unterhalt hat. */ + + for (u = r->units; u; u = u->next) { + int need = lifestyle(u); + + /* Erstmal zurücksetzen */ + freset(u, UFL_HUNGER); + + if (u->ship && (u->ship->flags & SF_FISHING)) { + unit *v; + int c = 2; + for (v = u; c > 0 && v; v = v->next) { + if (v->ship == u->ship) { + int get = 0; + if (v->number <= c) { + get = lifestyle(v); + } + else { + get = lifestyle(v) * c / v->number; + } + if (get) { + change_money(v, get); + } + } + c -= v->number; + } + u->ship->flags -= SF_FISHING; + } + + if (food_rules & FOOD_FROM_PEASANTS) { + struct faction *owner = region_get_owner(r); + /* if the region is owned, and the owner is nice, then we'll get + * food from the peasants - should not be used with WORK */ + if (owner != NULL && (get_alliance(owner, u->faction) & HELP_MONEY)) { + int rm = rmoney(r); + int use = _min(rm, need); + rsetmoney(r, rm - use); + need -= use; + } + } + + need -= get_money(u); + if (need > 0) { + unit *v; + + for (v = r->units; need && v; v = v->next) { + if (v->faction == u->faction && help_money(v)) { + int give = get_money(v) - lifestyle(v); + give = _min(need, give); + if (give > 0) { + change_money(v, -give); + change_money(u, give); + need -= give; + } + } + } + } + } + + /* 2. Versorgung durch Fremde. Das Silber alliierter Einheiten wird + * entsprechend verteilt. */ + for (u = r->units; u; u = u->next) { + int need = lifestyle(u); + faction *f = u->faction; + + need -= _max(0, get_money(u)); + + if (need > 0) { + unit *v; + + if (food_rules & FOOD_FROM_OWNER) { + /* the owner of the region is the first faction to help out when you're hungry */ + faction *owner = region_get_owner(r); + if (owner && owner != u->faction) { + for (v = r->units; v; v = v->next) { + if (v->faction == owner && alliedunit(v, f, HELP_MONEY) + && help_money(v)) { + help_feed(v, u, &need); + break; + } + } + } + } + for (v = r->units; need && v; v = v->next) { + if (v->faction != f && alliedunit(v, f, HELP_MONEY) + && help_money(v)) { + help_feed(v, u, &need); + } + } + + /* Die Einheit hat nicht genug Geld zusammengekratzt und + * nimmt Schaden: */ + if (need > 0) { + int lspp = lifestyle(u) / u->number; + if (lspp > 0) { + int number = (need + lspp - 1) / lspp; + if (hunger(number, u)) + fset(u, UFL_HUNGER); + } + } + } + } + + /* 3. bestimmen, wie viele Bauern gefressen werden. + * bei fehlenden Bauern den Dämon hungern lassen + */ + for (u = r->units; u; u = u->next) { + if (u_race(u) == get_race(RC_DAEMON)) { + int hungry = u->number; + + /* use peasantblood before eating the peasants themselves */ + const struct potion_type *pt_blood = 0; + const resource_type *rt_blood = rt_find("peasantblood"); + if (rt_blood) { + pt_blood = rt_blood->ptype; + } + if (pt_blood) { + /* always start with the unit itself, then the first known unit that may have some blood */ + unit *donor = u; + while (donor != NULL && hungry > 0) { + int blut = get_effect(donor, pt_blood); + blut = _min(blut, hungry); + if (blut) { + change_effect(donor, pt_blood, -blut); + hungry -= blut; + } + if (donor == u) + donor = r->units; + while (donor != NULL) { + if (u_race(donor) == get_race(RC_DAEMON) && donor != u) { + if (get_effect(donor, pt_blood)) { + /* if he's in our faction, drain him: */ + if (donor->faction == u->faction) + break; + } + } + donor = donor->next; + } + } + } + /* remaining demons feed on peasants */ + if (pl == NULL || !fval(pl, PFL_NOFEED)) { + if (peasantfood >= hungry) { + peasantfood -= hungry; + hungry = 0; + } + else { + hungry -= peasantfood; + peasantfood = 0; + } + if (hungry > 0) { + static int demon_hunger = -1; + if (demon_hunger < 0) { + demon_hunger = get_param_int(global.parameters, "hunger.demons", 0); + } + if (demon_hunger == 0) { + /* demons who don't feed are hungry */ + if (hunger(hungry, u)) + fset(u, UFL_HUNGER); + } + else { + /* no damage, but set the hungry-flag */ + fset(u, UFL_HUNGER); + } + } + } + } + } + rsetpeasants(r, peasantfood / 10); + + /* 3. Von den überlebenden das Geld abziehen: */ + for (u = r->units; u; u = u->next) { + int need = _min(get_money(u), lifestyle(u)); + change_money(u, -need); + } +} diff --git a/src/upkeep.h b/src/upkeep.h new file mode 100644 index 000000000..b0e02cf10 --- /dev/null +++ b/src/upkeep.h @@ -0,0 +1,22 @@ +#ifndef UPKEEP_H +#define UPKEEP_H +#ifdef __cplusplus +extern "C" { +#endif + + struct region; + struct unit; + void get_food(struct region * r); + int lifestyle(const struct unit * u); + + enum { + FOOD_FROM_PEASANTS = 1, + FOOD_FROM_OWNER = 2, + FOOD_IS_FREE = 4 + }; + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/upkeep.test.c b/src/upkeep.test.c new file mode 100644 index 000000000..893cf6b17 --- /dev/null +++ b/src/upkeep.test.c @@ -0,0 +1,170 @@ +#include +#include "upkeep.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +void test_upkeep_default(CuTest * tc) +{ + region *r; + unit *u1, *u2; + faction *f1, *f2; + const item_type *i_silver; + + test_cleanup(); + test_create_world(); + + i_silver = it_find("money"); + assert(i_silver); + r = findregion(0, 0); + f1 = test_create_faction(test_create_race("human")); + f2 = test_create_faction(test_create_race("human")); + assert(f1 && f2); + u1 = test_create_unit(f1, r); + u2 = test_create_unit(f2, r); + assert(r && u1 && u2); + + set_param(&global.parameters, "rules.economy.food", "0"); + i_change(&u1->items, i_silver, 20); + get_food(r); + // since u1 and u2 are not allied, u1 should not help u2 with upkeep + CuAssertIntEquals(tc, 10, i_get(u1->items, i_silver)); + CuAssertIntEquals(tc, 0, fval(u1, UFL_HUNGER)); + CuAssertIntEquals(tc, UFL_HUNGER, fval(u2, UFL_HUNGER)); + + test_cleanup(); +} + +void test_upkeep_hunger_damage(CuTest * tc) +{ + region *r; + unit *u1; + faction *f1; + const item_type *i_silver; + + test_cleanup(); + test_create_world(); + + i_silver = it_find("money"); + assert(i_silver); + r = findregion(0, 0); + f1 = test_create_faction(test_create_race("human")); + u1 = test_create_unit(f1, r); + assert(r && u1); + + set_param(&global.parameters, "rules.economy.food", "0"); + u1->hp = 100; + get_food(r); + // since u1 and u2 are not allied, u1 should not help u2 with upkeep + CuAssertTrue(tc, u1->hp<100); + + test_cleanup(); +} + +void test_upkeep_from_pool(CuTest * tc) +{ + region *r; + unit *u1, *u2; + const item_type *i_silver; + + test_cleanup(); + test_create_world(); + + i_silver = it_find("money"); + assert(i_silver); + r = findregion(0, 0); + u1 = test_create_unit(test_create_faction(test_create_race("human")), r); + u2 = test_create_unit(u1->faction, r); + assert(r && u1 && u2); + + set_param(&global.parameters, "rules.economy.food", "0"); + i_change(&u1->items, i_silver, 30); + get_food(r); + CuAssertIntEquals(tc, 10, i_get(u1->items, i_silver)); + CuAssertIntEquals(tc, 0, fval(u1, UFL_HUNGER)); + CuAssertIntEquals(tc, 0, fval(u2, UFL_HUNGER)); + get_food(r); + CuAssertIntEquals(tc, 0, i_get(u1->items, i_silver)); + CuAssertIntEquals(tc, 0, fval(u1, UFL_HUNGER)); + CuAssertIntEquals(tc, UFL_HUNGER, fval(u2, UFL_HUNGER)); + + test_cleanup(); +} + + +void test_upkeep_from_friend(CuTest * tc) +{ + region *r; + unit *u1, *u2; + faction *f1, *f2; + const item_type *i_silver; + + test_cleanup(); + test_create_world(); + + i_silver = it_find("money"); + assert(i_silver); + r = findregion(0, 0); + f1 = test_create_faction(test_create_race("human")); + f2 = test_create_faction(test_create_race("human")); + assert(f1 && f2); + set_alliance(f1, f2, HELP_MONEY); + u1 = test_create_unit(f1, r); + u2 = test_create_unit(f2, r); + assert(r && u1 && u2); + + set_param(&global.parameters, "rules.economy.food", "0"); + i_change(&u1->items, i_silver, 30); + get_food(r); + CuAssertIntEquals(tc, 10, i_get(u1->items, i_silver)); + CuAssertIntEquals(tc, 0, fval(u1, UFL_HUNGER)); + CuAssertIntEquals(tc, 0, fval(u2, UFL_HUNGER)); + get_food(r); + CuAssertIntEquals(tc, 0, i_get(u1->items, i_silver)); + CuAssertIntEquals(tc, 0, fval(u1, UFL_HUNGER)); + CuAssertIntEquals(tc, UFL_HUNGER, fval(u2, UFL_HUNGER)); + + test_cleanup(); +} + +void test_upkeep_free(CuTest * tc) +{ + region *r; + unit *u; + const item_type *i_silver; + + test_cleanup(); + test_create_world(); + + i_silver = it_find("money"); + assert(i_silver); + r = findregion(0, 0); + u = test_create_unit(test_create_faction(test_create_race("human")), r); + assert(r && u); + + set_param(&global.parameters, "rules.economy.food", "4"); // FOOD_IS_FREE + get_food(r); + CuAssertIntEquals(tc, 0, i_get(u->items, i_silver)); + CuAssertIntEquals(tc, 0, fval(u, UFL_HUNGER)); + + test_cleanup(); +} + +CuSuite *get_upkeep_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_upkeep_default); + SUITE_ADD_TEST(suite, test_upkeep_from_pool); + SUITE_ADD_TEST(suite, test_upkeep_from_friend); + SUITE_ADD_TEST(suite, test_upkeep_hunger_damage); + SUITE_ADD_TEST(suite, test_upkeep_free); + return suite; +}