diff --git a/src/alchemy.c b/src/alchemy.c index ace6a62dd..a1dd86c0a 100644 --- a/src/alchemy.c +++ b/src/alchemy.c @@ -77,7 +77,7 @@ void herbsearch(unit * u, int max) herbsfound = ntimespprob(effsk * u->number, (double)rherbs(r) / 100.0F, -0.01F); herbsfound = _min(herbsfound, max); - rsetherbs(r, rherbs(r) - herbsfound); + rsetherbs(r, (short) (rherbs(r) - herbsfound)); if (herbsfound) { produceexp(u, SK_HERBALISM, u->number); diff --git a/src/creport.c b/src/creport.c index 80ce1ba91..9b0182252 100644 --- a/src/creport.c +++ b/src/creport.c @@ -1380,7 +1380,7 @@ static void cr_output_region(FILE * F, report_context * ctx, seen_region * sr) } } if (r->land && r->land->ownership) { - fprintf(F, "%d;morale\n", r->land->morale); + fprintf(F, "%d;morale\n", region_get_morale(r)); } } diff --git a/src/economy.c b/src/economy.c index 0a360c094..6311d0c3a 100644 --- a/src/economy.c +++ b/src/economy.c @@ -2298,7 +2298,7 @@ static void plant(unit * u, int raw) /* Alles ok. Abziehen. */ use_pooled(u, rt_water, GET_DEFAULT, 1); use_pooled(u, itype->rtype, GET_DEFAULT, n); - rsetherbs(r, rherbs(r) + planted); + rsetherbs(r, (short) (rherbs(r) + planted)); ADDMSG(&u->faction->msgs, msg_message("plant", "unit region amount herb", u, r, planted, itype->rtype)); } diff --git a/src/economy.test.c b/src/economy.test.c index 8cf337f4f..577c7931a 100644 --- a/src/economy.test.c +++ b/src/economy.test.c @@ -224,7 +224,7 @@ static void test_tax_cmd(CuTest *tc) { - r->land->money = 11; + rsetmoney(r, 11); expandtax(r, taxorders); CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "income")); /* taxing is in multiples of 10 */ @@ -232,7 +232,7 @@ static void test_tax_cmd(CuTest *tc) { test_clear_messages(u->faction); i_change(&u->items, silver, -i_get(u->items, silver)); - r->land->money = 1000; + rsetmoney(r, 1000); taxorders = 0; tax_cmd(u, ord, &taxorders); expandtax(r, taxorders); diff --git a/src/give.test.c b/src/give.test.c index c6c3aafc8..119c5f1a9 100644 --- a/src/give.test.c +++ b/src/give.test.c @@ -64,7 +64,7 @@ static void test_give_unit_to_peasants(CuTest * tc) { rsetpeasants(env.r, 0); give_unit(env.src, NULL, NULL); CuAssertIntEquals(tc, 0, env.src->number); - CuAssertIntEquals(tc, 1, env.r->land->peasants); + CuAssertIntEquals(tc, 1, rpeasants(env.r)); test_cleanup(); } @@ -251,7 +251,7 @@ static void test_give_peasants(CuTest * tc) { msg = disband_men(1, env.src, NULL); CuAssertStrEquals(tc, "give_person_peasants", (const char*)msg->parameters[0].v); CuAssertIntEquals(tc, 0, env.src->number); - CuAssertIntEquals(tc, 1, env.r->land->peasants); + CuAssertIntEquals(tc, 1, rpeasants(env.r)); msg_release(msg); test_cleanup(); } diff --git a/src/gmtool.c b/src/gmtool.c index 92080b63b..36f9fe753 100644 --- a/src/gmtool.c +++ b/src/gmtool.c @@ -385,9 +385,8 @@ static void paint_info_region(window * wnd, const state * st) line++; mvwprintw(win, line++, 1, "%s, age %d", r->terrain->_name, r->age); if (r->land) { - mvwprintw(win, line++, 1, "$:%6d P:%5d", r->land->money, - r->land->peasants); - mvwprintw(win, line++, 1, "H:%6d %s:%5d", r->land->horses, + mvwprintw(win, line++, 1, "$:%6d P:%5d", rmoney(r), rpeasants(r)); + mvwprintw(win, line++, 1, "H:%6d %s:%5d", rhorses(r), (r->flags & RF_MALLORN) ? "M" : "T", r->land->trees[1] + r->land->trees[2]); } diff --git a/src/kernel/item.c b/src/kernel/item.c index 1b312c655..aa975fb8e 100644 --- a/src/kernel/item.c +++ b/src/kernel/item.c @@ -98,8 +98,8 @@ static int res_changehp(unit * u, const resource_type * rtype, int delta) static int res_changepeasants(unit * u, const resource_type * rtype, int delta) { assert(rtype != NULL && u->region->land); - u->region->land->peasants += delta; - return u->region->land->peasants; + rsetpeasants(u->region, rpeasants(u->region) + delta); + return rpeasants(u->region); } static int golem_factor(const unit *u, const resource_type *rtype) { diff --git a/src/kernel/region.c b/src/kernel/region.c index 3abbdf883..aa4c86425 100644 --- a/src/kernel/region.c +++ b/src/kernel/region.c @@ -595,26 +595,30 @@ bool is_coastregion(region * r) int rpeasants(const region * r) { - return ((r)->land ? (r)->land->peasants : 0); + return r->land ? r->land->peasants : 0; } void rsetpeasants(region * r, int value) { - if (r->land) r->land->peasants = value; - else assert(value>=0); - + if (r->land) { + assert(value >= 0); + r->land->peasants = value; + } else + assert(value == 0); } int rmoney(const region * r) { - return ((r)->land ? (r)->land->money : 0); + return r->land ? r->land->money : 0; } void rsethorses(const region * r, int value) { - assert(value >= 0); - if (r->land) + if (r->land) { + assert(value >= 0); r->land->horses = value; + } else + assert(value == 0); } int rhorses(const region * r) @@ -624,10 +628,28 @@ int rhorses(const region * r) void rsetmoney(region * r, int value) { - if (r->land) r->land->money = value; - else assert(value >= 0); + if (r->land) { + assert(value >= 0); + r->land->money = value; + } else + assert(value == 0); } +int rherbs(const struct region *r) +{ + return r->land?r->land->herbs:0; +} + +void rsetherbs(const struct region *r, int value) +{ + if (r->land) { + assert(value >= 0); + r->land->herbs = (short)(value); + } else + assert(value == 0); +} + + void r_setdemand(region * r, const luxury_type * ltype, int value) { struct demand *d, **dp = &r->land->demands; @@ -1325,7 +1347,7 @@ faction *update_owners(region * r) else if (f || new_owner->faction != region_get_last_owner(r)) { alliance *al = region_get_alliance(r); if (al && new_owner->faction->alliance == al) { - int morale = _max(0, r->land->morale - MORALE_TRANSFER); + int morale = _max(0, region_get_morale(r) - MORALE_TRANSFER); region_set_morale(r, morale, turn); } else { diff --git a/src/kernel/region.h b/src/kernel/region.h index 210ded0e1..5974fd96c 100644 --- a/src/kernel/region.h +++ b/src/kernel/region.h @@ -201,13 +201,14 @@ extern "C" { int rhorses(const struct region *r); void rsethorses(const struct region *r, int value); + int rherbs(const struct region *r); + void rsetherbs(const struct region *r, int value); + #define rbuildings(r) ((r)->buildings) #define rherbtype(r) ((r)->land?(r)->land->herbtype:0) #define rsetherbtype(r, value) if ((r)->land) (r)->land->herbtype=(value) -#define rherbs(r) ((r)->land?(r)->land->herbs:0) -#define rsetherbs(r, value) if ((r)->land) ((r)->land->herbs=(short)(value)) bool r_isforest(const struct region *r); diff --git a/src/kernel/save.c b/src/kernel/save.c index f14c610ff..0fae07bb4 100644 --- a/src/kernel/save.c +++ b/src/kernel/save.c @@ -973,10 +973,7 @@ static region *readregion(struct gamedata *data, int x, int y) read_items(data->store, &r->land->items); if (data->version >= REGIONOWNER_VERSION) { READ_INT(data->store, &n); - r->land->morale = (short)n; - if (r->land->morale < 0) { - r->land->morale = 0; - } + region_set_morale(r, _max(0, (short) n), -1); read_owner(data, &r->land->ownership); } } @@ -1039,7 +1036,7 @@ void writeregion(struct gamedata *data, const region * r) write_items(data->store, r->land->items); WRITE_SECTION(data->store); #if RELEASE_VERSION>=REGIONOWNER_VERSION - WRITE_INT(data->store, r->land->morale); + WRITE_INT(data->store, region_get_morale(r)); write_owner(data, r->land->ownership); WRITE_SECTION(data->store); #endif diff --git a/src/laws.c b/src/laws.c index 591e865a9..7dc6cd0af 100755 --- a/src/laws.c +++ b/src/laws.c @@ -707,23 +707,21 @@ void immigration(void) int rp = rpeasants(r) + r->land->newpeasants; rsetpeasants(r, _max(0, rp)); } - /* Genereate some (0-2 to 0-6 depending on the income) peasants out of nothing */ - /*if less then 50 are in the region and there is space and no monster or deamon units in the region */ + /* Genereate some (0-6 depending on the income) peasants out of nothing */ + /* if less than 50 are in the region and there is space and no monster or demon units in the region */ int peasants = rpeasants(r); - if (repopulate && r->land && (peasants < repopulate) && maxworkingpeasants(r) >(peasants + 30) * 2) - { + int income = wage(r, NULL, NULL, turn) - maintenance_cost(NULL); + if (repopulate && r->land && (peasants < repopulate) && maxworkingpeasants(r) > (peasants + 30) * 2 && income >= 0) { int badunit = 0; unit *u; for (u = r->units; u; u = u->next) { - if (!playerrace(u_race(u)) || u_race(u) == get_race(RC_DAEMON)) - { + if (!playerrace(u_race(u)) || u_race(u) == get_race(RC_DAEMON)) { badunit = 1; break; } } - if (badunit == 0) - { - peasants += (int)(rng_double()*(wage(r, NULL, NULL, turn) - 9)); + if (badunit == 0) { + peasants += (int)(rng_double() * (income + 1)); rsetpeasants(r, peasants); } } diff --git a/src/laws.test.c b/src/laws.test.c index 12c4e93f4..91b04b257 100644 --- a/src/laws.test.c +++ b/src/laws.test.c @@ -1258,6 +1258,43 @@ static void test_show_without_item(CuTest *tc) test_cleanup(); } +static int low_wage(const region * r, const faction * f, const race * rc, int in_turn) { + return 1; +} + +static void test_immigration(CuTest * tc) +{ + region *r; + double inject[] = { 1 }; + int (*old_wage)(const region*, const faction*, const race*, int) = global.functions.wage; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + + rsetpeasants(r, 0); + config_set("rules.economy.repopulate_maximum", 0); + + random_source_inject_constant(0); + + immigration(); + CuAssertIntEquals(tc, 0, rpeasants(r)); + + random_source_inject_constant(1); + + immigration(); + CuAssertIntEquals(tc, 2, rpeasants(r)); + + random_source_inject_array(inject, 2); + + global.functions.wage = low_wage; + immigration(); + CuAssertIntEquals(tc, 2, rpeasants(r)); + global.functions.wage = old_wage; + + test_cleanup(); +} + CuSuite *get_laws_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -1316,6 +1353,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_immigration); return suite; } diff --git a/src/modules/autoseed.c b/src/modules/autoseed.c index f0af4582f..e8855683e 100644 --- a/src/modules/autoseed.c +++ b/src/modules/autoseed.c @@ -842,7 +842,7 @@ int region_quality(const region * r, region * rn[]) /* nobody likes volcanoes */ result -= 2000; } - result += rn[n]->land->peasants; + result += rpeasants(rn[n]); } } return result; @@ -1007,10 +1007,10 @@ int build_island_e3(newfaction ** players, int x, int y, int numfactions, int mi terraform_region(r, newterrain(T_HIGHLAND)); prepare_starting_region(r); } - r->land->money = 50000; /* 2% = 1000 silver */ + rsetmoney(r, 50000); /* 2% = 1000 silver */ } else if (r->land) { - r->land->money *= 4; + rsetmoney(r, rmoney(r) *4); } } return nfactions; diff --git a/src/monsters.c b/src/monsters.c index ab63cbb31..b48cb7c82 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -657,8 +657,8 @@ static order *plan_dragon(unit * u) order *long_order = NULL; if (ta == NULL) { - move |= (r->land == 0 || r->land->peasants == 0); /* when no peasants, move */ - move |= (r->land == 0 || r->land->money == 0); /* when no money, move */ + move |= (rpeasants(r) == 0); /* when no peasants, move */ + move |= (rmoney(r) == 0); /* when no money, move */ } move |= chance(0.04); /* 4% chance to change your mind */ @@ -950,7 +950,7 @@ void spawn_undead(void) continue; /* Chance 0.1% * chaosfactor */ - if (r->land && unburied > r->land->peasants / 20 + if (r->land && unburied > rpeasants(r) / 20 && rng_int() % 10000 < (100 + 100 * chaosfactor(r))) { message *msg; unit *u; diff --git a/src/morale.c b/src/morale.c index c0a20f372..8dc22ccb5 100644 --- a/src/morale.c +++ b/src/morale.c @@ -29,6 +29,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include +#include + static double rc_popularity(const struct race *rc) { int pop = get_param_int(rc->parameters, "morale", MORALE_AVERAGE); @@ -36,6 +38,9 @@ static double rc_popularity(const struct race *rc) } void morale_update(region *r) { + int morale = region_get_morale(r); + assert(r->land); + if (r->land->ownership && r->land->ownership->owner) { int stability = turn - r->land->ownership->morale_turn; int maxmorale = MORALE_DEFAULT; @@ -44,24 +49,24 @@ void morale_update(region *r) { int bsize = buildingeffsize(b, false); maxmorale = (int)(0.5 + b->type->taxes(b, bsize + 1) / MORALE_TAX_FACTOR); } - if (r->land->morale < maxmorale) { + if (morale < maxmorale) { if (stability > MORALE_COOLDOWN && r->land->ownership->owner - && r->land->morale < MORALE_MAX) { + && morale < MORALE_MAX) { double ch = rc_popularity(r->land->ownership->owner->race); if (is_cursed(r->attribs, C_GENEROUS, 0)) { ch *= 1.2; /* 20% improvement */ } if (stability >= MORALE_AVERAGE * 2 || chance(ch)) { - region_set_morale(r, r->land->morale + 1, turn); + region_set_morale(r, morale + 1, turn); } } } - else if (r->land->morale > maxmorale) { - region_set_morale(r, r->land->morale - 1, turn); + else if (morale > maxmorale) { + region_set_morale(r, morale - 1, turn); } } - else if (r->land->morale > MORALE_DEFAULT) { - region_set_morale(r, r->land->morale - 1, turn); + else if (morale > MORALE_DEFAULT) { + region_set_morale(r, morale - 1, turn); } } diff --git a/src/randenc.c b/src/randenc.c index 7f27fed4d..d948ea270 100644 --- a/src/randenc.c +++ b/src/randenc.c @@ -604,6 +604,7 @@ volcano_destruction(region * volcano, region * r, const char *damage) unit *u = *up; if (u->number) { int dead = damage_unit(u, damage, true, false); + /* TODO create undead */ if (dead) { ADDMSG(&u->faction->msgs, msg_message("volcano_dead", "unit region dead", u, volcano, dead)); diff --git a/src/report.c b/src/report.c index 9125cebdb..6e13e834f 100644 --- a/src/report.c +++ b/src/report.c @@ -1019,7 +1019,7 @@ static void describe(stream *out, const seen_region * sr, faction * f) if (r->land->ownership) { const char *str = - LOC(f->locale, mkname("morale", itoa10(r->land->morale))); + LOC(f->locale, mkname("morale", itoa10(region_get_morale(r)))); bytes = _snprintf(bufp, size, " %s", str); if (wrptr(&bufp, &size, bytes) != 0) WARN_STATIC_BUFFER(); @@ -1354,7 +1354,7 @@ static void statistics(stream *out, const region * r, const faction * f) } if (r->land->ownership) { - m = msg_message("nr_stat_morale", "morale", r->land->morale); + m = msg_message("nr_stat_morale", "morale", region_get_morale(r)); nr_render(m, f->locale, buf, sizeof(buf), f); paragraph(out, buf, 2, 2, 0); msg_release(m); diff --git a/src/tests.c b/src/tests.c index 9fc810afe..eb03820a8 100644 --- a/src/tests.c +++ b/src/tests.c @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -103,6 +104,8 @@ void test_cleanup(void) errno = 0; log_error("errno: %d", error); } + + random_source_reset(); } terrain_type * diff --git a/src/util/rand.c b/src/util/rand.c index 4e600b1a5..1825340fe 100644 --- a/src/util/rand.c +++ b/src/util/rand.c @@ -21,6 +21,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "rng.h" #include +#include #include #include #include @@ -65,3 +66,66 @@ bool chance(double x) return true; return rng_double() < x; } + +extern double genrand_real2(void); + + +typedef struct random_source { + double (*double_source) (void); +} random_source; + +random_source *r_source = 0; + +double rng_injectable_double(void) { + if (r_source) + return r_source->double_source(); + return genrand_real2(); +} + +static double constant_value; + +static double constant_source (void) { + return constant_value; +} + +struct random_source constant_provider = { + constant_source +}; + +void random_source_inject_constant(double value) { + constant_value = value; + r_source = &constant_provider; +} + +static int i = 0; +static double *values; +static int value_size = 0; + +static double array_source (void) { + assert(i 0); + value_size = size; + if (values) + free(values); + values = malloc(sizeof(double) * size); + for (i=0; i < size; ++i) { + values[i] = inject[i]; + } + i = 0; + r_source = &array_provider; +} + +void random_source_reset(void) { + if (values) + free(values); + values = NULL; + r_source = NULL; +} diff --git a/src/util/rand.h b/src/util/rand.h index b161f31cf..e9e5d19ee 100644 --- a/src/util/rand.h +++ b/src/util/rand.h @@ -31,6 +31,15 @@ extern "C" { extern int ntimespprob(int n, double p, double mod); extern bool chance(double x); + /* a random source that generates numbers in [0, 1). + By calling the random_source_inject... functions you can set a special random source, + which is handy for testing */ + double rng_injectable_double(void); + + void random_source_inject_constant(double value); + void random_source_inject_array(double inject[], int size); + void random_source_reset(void); + #ifdef __cplusplus } #endif diff --git a/src/util/rng.h b/src/util/rng.h index 1cc95d094..1bc3e48ed 100644 --- a/src/util/rng.h +++ b/src/util/rng.h @@ -23,7 +23,7 @@ extern "C" { extern unsigned long genrand_int32(void); /* generates a random number on [0,1)-real-interval */ - extern double genrand_real2(void); + extern double rng_injectable_double(void); /* generates a random number on [0,0x7fffffff]-interval */ long genrand_int31(void); @@ -31,7 +31,7 @@ extern "C" { # define rng_init(seed) init_genrand(seed) # define rng_int (int)genrand_int31 # define rng_uint (unsigned int)genrand_int32 -# define rng_double genrand_real2 +# define rng_double rng_injectable_double # define RNG_RAND_MAX 0x7fffffff #else # include