server/src/kernel/region.c
2019-09-13 21:35:44 +02:00

1513 lines
36 KiB
C

#ifdef _MSC_VER
# include <platform.h>
#endif
#include "region.h"
/* kernel includes */
#include "alliance.h"
#include "building.h"
#include "calendar.h"
#include "config.h"
#include "connection.h"
#include "curse.h"
#include "equipment.h"
#include "faction.h"
#include "item.h"
#include "messages.h"
#include "plane.h"
#include "region.h"
#include "resources.h"
#include "ship.h"
#include "terrain.h"
#include "terrainid.h"
#include "unit.h"
#include <spells/regioncurse.h>
/* util includes */
#include <util/assert.h>
#include <kernel/attrib.h>
#include <kernel/gamedata.h>
#include <util/strings.h>
#include <util/lists.h>
#include <util/log.h>
#include <util/resolve.h>
#include <util/umlaut.h>
#include <util/language.h>
#include <util/rand.h>
#include <util/rng.h>
#include <util/strings.h>
#include <storage.h>
#include <modules/autoseed.h>
/* libc includes */
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern int dice_rand(const char *s);
region *regions;
int get_maxluxuries(void)
{
const luxury_type *ltype;
int maxluxuries = 0;
for (ltype = luxurytypes; ltype; ltype = ltype->next) {
++maxluxuries;
}
return maxluxuries;
}
const int delta_x[MAXDIRECTIONS] = {
-1, 0, 1, 1, 0, -1
};
const int delta_y[MAXDIRECTIONS] = {
1, 1, 0, -1, -1, 0
};
static const direction_t back[MAXDIRECTIONS] = {
D_SOUTHEAST,
D_SOUTHWEST,
D_WEST,
D_NORTHWEST,
D_NORTHEAST,
D_EAST,
};
direction_t dir_invert(direction_t dir)
{
switch (dir) {
case D_PAUSE:
case D_SPECIAL:
return dir;
break;
default:
if (dir >= 0 && dir < MAXDIRECTIONS)
return back[dir];
}
assert(!"illegal direction");
return NODIRECTION;
}
const char *write_regionname(const region * r, const faction * f, char *buffer,
size_t size)
{
char *buf = (char *)buffer;
const struct locale *lang = f ? f->locale : 0;
if (r == NULL) {
str_strlcpy(buf, "(null)", size);
}
else {
plane *pl = rplane(r);
int nx = r->x, ny = r->y;
pnormalize(&nx, &ny, pl);
adjust_coordinates(f, &nx, &ny, pl);
snprintf(buf, size, "%s (%d,%d)", rname(r, lang), nx, ny);
}
return buffer;
}
const char *regionname(const region * r, const faction * f)
{
static int index = 0;
static char buf[2][NAMESIZE];
index = 1 - index;
return write_regionname(r, f, buf[index], sizeof(buf[index]));
}
int region_maxworkers(const region *r)
{
int size = max_production(r);
int treespace = size - (rtrees(r, 2) + rtrees(r, 1) / 2) * TREESIZE;
size /=10;
if (size > 200) size = 200;
if (treespace < size) treespace = size;
return treespace;
}
int deathcount(const region * r)
{
attrib *a = a_find(r->attribs, &at_deathcount);
if (!a)
return 0;
return a->data.i;
}
void deathcounts(region * r, int fallen)
{
attrib *a = NULL;
if (fallen == 0)
return;
if (r->attribs) {
const curse_type *ctype = &ct_holyground;
if (ctype && curse_active(get_curse(r->attribs, ctype)))
return;
a = a_find(r->attribs, &at_deathcount);
}
if (!a) {
a = a_add(&r->attribs, a_new(&at_deathcount));
}
a->data.i += fallen;
if (a->data.i <= 0) {
a_remove(&r->attribs, a);
}
}
/* Moveblock wird zur Zeit nicht ueber Attribute, sondern ein Bitfeld
r->moveblock gemacht. Sollte umgestellt werden, wenn kompliziertere
Dinge gefragt werden. */
/********************/
/* at_moveblock */
/********************/
void a_initmoveblock(variant *var)
{
var->v = calloc(1, sizeof(moveblock));
}
int a_readmoveblock(variant *var, void *owner, gamedata *data)
{
moveblock *m = (moveblock *)var->v;
int i;
READ_INT(data->store, &i);
m->dir = (direction_t)i;
return AT_READ_OK;
}
void
a_writemoveblock(const variant *var, const void *owner, struct storage *store)
{
moveblock *m = (moveblock *)var->v;
WRITE_INT(store, (int)m->dir);
}
attrib_type at_moveblock = {
"moveblock", a_initmoveblock, NULL, NULL, a_writemoveblock, a_readmoveblock
};
#define coor_hashkey(x, y) (unsigned int)((x<<16) + y)
#define RMAXHASH MAXREGIONS
static region *regionhash[RMAXHASH];
static int dummy_data;
static region *dummy_ptr = (region *)& dummy_data; /* a funny hack */
typedef struct uidhashentry {
int uid;
region *r;
} uidhashentry;
static uidhashentry uidhash[MAXREGIONS];
struct region *findregionbyid(int uid)
{
int key = uid % MAXREGIONS;
while (uidhash[key].uid != 0 && uidhash[key].uid != uid) {
if (++key == MAXREGIONS) key = 0;
}
return uidhash[key].r;
}
#define DELMARKER dummy_ptr
static void unhash_uid(region * r)
{
int key = r->uid % MAXREGIONS;
assert(r->uid);
while (uidhash[key].uid != 0 && uidhash[key].uid != r->uid) {
if (++key == MAXREGIONS) key = 0;
}
assert(uidhash[key].r == r);
uidhash[key].r = NULL;
}
static void rhash_uid(region * r)
{
int uid = r->uid;
for (;;) {
if (uid != 0) {
int key = uid % MAXREGIONS;
while (uidhash[key].uid != 0 && uidhash[key].uid != uid) {
if (++key == MAXREGIONS) key = 0;
}
if (uidhash[key].uid == 0) {
uidhash[key].uid = uid;
uidhash[key].r = r;
break;
}
assert(uidhash[key].r != r || !"duplicate registration");
}
r->uid = uid = rng_int();
}
}
#define HASH_STATISTICS 1
#if HASH_STATISTICS
static int hash_requests;
static int hash_misses;
#endif
void pnormalize(int *x, int *y, const plane * pl)
{
if (pl) {
if (x) {
int width = pl->maxx - pl->minx + 1;
int nx = *x - pl->minx;
nx = (nx > 0) ? nx : (width - (-nx) % width);
*x = nx % width + pl->minx;
}
if (y) {
int height = pl->maxy - pl->miny + 1;
int ny = *y - pl->miny;
ny = (ny > 0) ? ny : (height - (-ny) % height);
*y = ny % height + pl->miny;
}
}
}
static region *rfindhash(int x, int y)
{
unsigned int rid = coor_hashkey(x, y);
int key = HASH1(rid, RMAXHASH), gk = HASH2(rid, RMAXHASH);
#if HASH_STATISTICS
++hash_requests;
#endif
while (regionhash[key] != NULL && (regionhash[key] == DELMARKER
|| regionhash[key]->x != x || regionhash[key]->y != y)) {
key = (key + gk) % RMAXHASH;
#if HASH_STATISTICS
++hash_misses;
#endif
}
return regionhash[key];
}
void rhash(region * r)
{
unsigned int rid = coor_hashkey(r->x, r->y);
int key = HASH1(rid, RMAXHASH), gk = HASH2(rid, RMAXHASH);
while (regionhash[key] != NULL && regionhash[key] != DELMARKER
&& regionhash[key] != r) {
key = (key + gk) % RMAXHASH;
}
assert(regionhash[key] != r || !"trying to add the same region twice");
regionhash[key] = r;
}
void runhash(region * r)
{
unsigned int rid = coor_hashkey(r->x, r->y);
int key = HASH1(rid, RMAXHASH), gk = HASH2(rid, RMAXHASH);
int d, di;
for (d = 0, di = MAXDIRECTIONS / 2; d != MAXDIRECTIONS; ++d, ++di) {
region *rc = r->connect[d];
if (rc != NULL) {
if (di >= MAXDIRECTIONS)
di -= MAXDIRECTIONS;
rc->connect[di] = NULL;
r->connect[d] = NULL;
}
}
while (regionhash[key] != NULL && regionhash[key] != r) {
key = (key + gk) % RMAXHASH;
}
assert(regionhash[key] == r || !"trying to remove a unit that is not hashed");
regionhash[key] = DELMARKER;
}
region *r_connect(const region * r, direction_t dir)
{
region *result;
int x, y;
region *rmodify = (region *)r;
assert(dir >= 0 && dir < MAXDIRECTIONS);
if (r->connect[dir]) {
return r->connect[dir];
}
assert(dir < MAXDIRECTIONS);
x = r->x + delta_x[dir];
y = r->y + delta_y[dir];
pnormalize(&x, &y, rplane(r));
result = rfindhash(x, y);
if (result) {
rmodify->connect[dir] = result;
result->connect[back[dir]] = rmodify;
}
return result;
}
region *findregion(int x, int y)
{
return rfindhash(x, y);
}
/* Contributed by Hubert Mackenberg. Thanks.
* x und y Abstand zwischen x1 und x2 berechnen
*/
static int koor_distance_orig(int x1, int y1, int x2, int y2)
{
int dx = x1 - x2;
int dy = y1 - y2;
/* Bei negativem dy am Ursprung spiegeln, das veraendert
* den Abstand nicht
*/
if (dy < 0) {
dy = -dy;
dx = -dx;
}
/*
* dy ist jetzt >=0, fuer dx sind 3 Faelle zu untescheiden
*/
if (dx >= 0) {
int result = dx + dy;
return result;
}
else if (-dx >= dy) {
int result = -dx;
return result;
}
else {
return dy;
}
}
static int
koor_distance_wrap_xy(int x1, int y1, int x2, int y2, int width, int height)
{
int dx = x1 - x2;
int dy = y1 - y2;
int result, dist;
int mindist = ((width > height) ? height : width) / 2;
/* Bei negativem dy am Ursprung spiegeln, das veraendert
* den Abstand nicht
*/
if (dy < 0) {
dy = -dy;
dx = -dx;
}
if (dx < 0) {
dx = width + dx;
}
/* dx,dy is now pointing northeast */
result = dx + dy;
if (result <= mindist)
return result;
dist = (width - dx) + (height - dy); /* southwest */
if (dist >= 0 && dist < result) {
result = dist;
if (result <= mindist)
return result;
}
dist = height - dy;
if (dist < dx) dist = dx;
if (dist >= 0 && dist < result) {
result = dist;
if (result <= mindist)
return result;
}
dist = width - dx;
if (dist < dy) dist = dy;
if (dist >= 0 && dist < result)
result = dist;
return result;
}
int koor_distance(int x1, int y1, int x2, int y2)
{
const plane *p1 = findplane(x1, y1);
const plane *p2 = findplane(x2, y2);
if (p1 != p2)
return INT_MAX;
else {
int width = plane_width(p1);
int height = plane_height(p1);
if (width && height) {
return koor_distance_wrap_xy(x1, y1, x2, y2, width, height);
}
else {
return koor_distance_orig(x1, y1, x2, y2);
}
}
}
int distance(const region * r1, const region * r2)
{
return koor_distance(r1->x, r1->y, r2->x, r2->y);
}
void free_regionlist(region_list * rl)
{
while (rl) {
region_list *rl2 = rl->next;
free(rl);
rl = rl2;
}
}
void add_regionlist(region_list ** rl, region * r)
{
region_list *rl2 = (region_list *)malloc(sizeof(region_list));
if (!rl2) abort();
rl2->data = r;
rl2->next = *rl;
*rl = rl2;
}
/********************/
/* at_horseluck */
/********************/
attrib_type at_horseluck = {
"horseluck",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
NO_WRITE,
NO_READ,
NULL,
ATF_UNIQUE
};
/**********************/
/* at_peasantluck */
/**********************/
attrib_type at_peasantluck = {
"peasantluck",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
NO_WRITE,
NO_READ,
NULL,
ATF_UNIQUE
};
/*********************/
/* at_deathcount */
/*********************/
attrib_type at_deathcount = {
"deathcount",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
a_writeint,
a_readint,
NULL,
ATF_UNIQUE
};
/*********************/
/* at_woodcount */
/*********************/
attrib_type at_woodcount = {
"woodcount",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
NO_WRITE,
a_readint,
NULL,
ATF_UNIQUE
};
void rsetroad(region * r, direction_t d, int val)
{
connection *b;
region *r2 = rconnect(r, d);
assert(val>=SHRT_MIN && val<=SHRT_MAX);
if (!r2) {
return;
}
b = get_borders(r, r2);
while (b && b->type != &bt_road) {
b = b->next;
}
if (!b) {
if (!val) return;
b = new_border(&bt_road, r, r2);
}
if (r == b->from) {
b->data.sa[0] = (short)val;
}
else {
b->data.sa[1] = (short)val;
}
}
int rroad(const region * r, direction_t d)
{
connection *b;
region *r2 = rconnect(r, d);
if (!r2) {
return 0;
}
b = get_borders(r, r2);
while (b && b->type != &bt_road) {
b = b->next;
}
if (!b) {
return 0;
}
return (r == b->from) ? b->data.sa[0] : b->data.sa[1];
}
bool r_isforest(const region * r)
{
if (fval(r->terrain, FOREST_REGION)) {
/* needs to be covered with at least 48% trees */
int mincover = (int)(r->terrain->size * 0.48);
int trees = rtrees(r, 2) + rtrees(r, 1);
return (trees * TREESIZE >= mincover);
}
return false;
}
bool is_coastregion(region * r)
{
direction_t i;
int res = 0;
for (i = 0; !res && i < MAXDIRECTIONS; i++) {
region *rn = rconnect(r, i);
if (rn && fval(rn->terrain, SEA_REGION))
res++;
}
return res != 0;
}
int rpeasants(const region * r)
{
int value = 0;
if (r->land) {
value = r->land->peasants;
assert(value >= 0);
}
return value;
}
void rsetpeasants(region * r, int value)
{
assert(r->land || value==0);
assert(value >= 0);
if (r->land) {
if (value > USHRT_MAX) {
log_warning("region %s cannot have %d peasants.", regionname(r, NULL), value);
value = USHRT_MAX;
}
r->land->peasants = (unsigned short)value;
}
}
int rmoney(const region * r)
{
return r->land ? r->land->money : 0;
}
void rsethorses(const region * r, int value)
{
assert(r->land || value==0);
assert(value >= 0);
if (r->land) {
if (value > USHRT_MAX) {
log_warning("region %s cannot have %d horses.", regionname(r, NULL), value);
value = USHRT_MAX;
}
r->land->horses = (unsigned short)value;
}
}
int rhorses(const region * r)
{
return r->land ? r->land->horses : 0;
}
void rsetmoney(region * r, int value)
{
assert(r && (r->land || value==0));
assert(value >= 0);
if (r->land) {
r->land->money = value;
}
}
int rherbs(const region *r)
{
return r->land ? r->land->herbs : 0;
}
void rsetherbs(region *r, int value)
{
assert(r->land || value==0);
if (r->land) {
if (value > USHRT_MAX) {
log_warning("region %s cannot have %d herbs.", regionname(r, NULL), value);
value = USHRT_MAX;
}
r->land->herbs = (unsigned short)value;
}
}
void rsetherbtype(region *r, const struct item_type *itype) {
assert(r->land && r->terrain);
if (itype == NULL) {
r->land->herbtype = NULL;
}
else {
if (r->terrain->herbs) {
int i;
for (i = 0; r->terrain->herbs[i]; ++i) {
if (r->terrain->herbs[i] == itype) {
r->land->herbtype = itype;
return;
}
}
}
log_debug("attempt to set herbtype=%s for terrain=%s in %s", itype->rtype->_name, r->terrain->_name, regionname(r, 0));
r->land->herbtype = itype;
}
}
void r_setdemand(region * r, const luxury_type * ltype, int value)
{
struct demand *d, **dp = &r->land->demands;
if (ltype == NULL)
return;
while (*dp && (*dp)->type != ltype)
dp = &(*dp)->next;
d = *dp;
if (!d) {
d = *dp = malloc(sizeof(struct demand));
assert_alloc(d);
d->next = NULL;
d->type = ltype;
}
d->value = value;
}
const item_type *r_luxury(const region * r)
{
struct demand *dmd;
if (r->land) {
assert(r->land->demands || !"need to call fix_demand on a region");
for (dmd = r->land->demands; dmd; dmd = dmd->next) {
if (dmd->value == 0)
return dmd->type->itype;
}
}
return NULL;
}
int r_demand(const region * r, const luxury_type * ltype)
{
struct demand *d;
assert(r && r->land);
d = r->land->demands;
while (d && d->type != ltype)
d = d->next;
if (!d)
return -1;
return d->value;
}
const char *rname(const region * r, const struct locale *lang)
{
if (r->land && r->land->name) {
return r->land->name;
}
return LOC(lang, terrain_name(r));
}
int rtrees(const region * r, int ageclass)
{
return ((r)->land ? (r)->land->trees[ageclass] : 0);
}
int rsettrees(const region * r, int ageclass, int value)
{
if (!r->land) {
assert(value == 0);
}
else {
assert(value >= 0);
if (value <= MAXTREES) {
return r->land->trees[ageclass] = value;
}
else {
r->land->trees[ageclass] = MAXTREES;
}
}
return 0;
}
region *region_create(int uid)
{
region *r = (region *)calloc(1, sizeof(region));
assert_alloc(r);
r->uid = uid;
rhash_uid(r);
return r;
}
static region *last;
static unsigned int max_index;
void add_region(region *r, int x, int y) {
r->x = x;
r->y = y;
r->_plane = findplane(x, y);
rhash(r);
if (last) {
addlist(&last, r);
}
else {
addlist(&regions, r);
}
last = r;
assert(r->next == NULL);
r->index = ++max_index;
}
region *new_region(int x, int y, struct plane *pl, int uid)
{
region *r;
r = region_create(uid);
r->age = 1;
add_region(r, x, y);
assert(pl == r->_plane);
return r;
}
static region *deleted_regions;
void remove_region(region ** rlist, region * r)
{
assert(r);
while (r->units) {
unit *u = r->units;
i_freeall(&u->items);
remove_unit(&r->units, u);
}
runhash(r);
unhash_uid(r);
while (*rlist && *rlist != r)
rlist = &(*rlist)->next;
assert(*rlist == r);
*rlist = r->next;
r->next = deleted_regions;
deleted_regions = r;
}
void free_land(land_region * lr)
{
free(lr->ownership);
while (lr->demands) {
struct demand *d = lr->demands;
lr->demands = d->next;
free(d);
}
free(lr->name);
free(lr->display);
free(lr);
}
void region_setresource(region * r, const struct resource_type *rtype, int value)
{
rawmaterial *rm = r->resources;
while (rm) {
if (rm->rtype == rtype) {
rm->amount = value;
break;
}
rm = rm->next;
}
if (!rm) {
if (rtype == get_resourcetype(R_SILVER))
rsetmoney(r, value);
else if (rtype == get_resourcetype(R_PEASANT))
rsetpeasants(r, value);
else if (rtype == get_resourcetype(R_HORSE))
rsethorses(r, value);
else {
rawmaterial *rm;
if (r->terrain->production) {
int i;
for (i = 0; r->terrain->production[i].type; ++i) {
const terrain_production *production = r->terrain->production + i;
if (production->type == rtype) {
add_resource(r, 1, value, dice_rand(production->divisor), rtype);
return;
}
}
}
/* adamantium etc are not usually terraformed: */
for (rm = r->resources; rm; rm = rm->next) {
if (rm->rtype == rtype) {
rm->amount = value;
return;
}
}
if (!rm) {
add_resource(r, 1, value, 150, rtype);
}
}
}
}
int region_getresource_level(const region * r, const struct resource_type * rtype)
{
const rawmaterial *rm;
for (rm = r->resources; rm; rm = rm->next) {
if (rm->rtype == rtype) {
return rm->level;
}
}
return -1;
}
int region_getresource(const region * r, const struct resource_type *rtype)
{
const rawmaterial *rm;
for (rm = r->resources; rm; rm = rm->next) {
if (rm->rtype == rtype) {
return rm->amount;
}
}
if (rtype == get_resourcetype(R_SILVER))
return rmoney(r);
if (rtype == get_resourcetype(R_HORSE))
return rhorses(r);
if (rtype == get_resourcetype(R_PEASANT))
return rpeasants(r);
return 0;
}
void free_region(region * r)
{
if (last == r)
last = NULL;
if (r->land)
free_land(r->land);
if (r->msgs) {
free_messagelist(r->msgs->begin);
free(r->msgs);
r->msgs = 0;
}
while (r->individual_messages) {
struct individual_message *msg = r->individual_messages;
r->individual_messages = msg->next;
if (msg->msgs) {
free_messagelist(msg->msgs->begin);
free(msg->msgs);
}
free(msg);
}
while (r->attribs)
a_remove(&r->attribs, r->attribs);
while (r->resources) {
rawmaterial *res = r->resources;
r->resources = res->next;
free(res);
}
while (r->units) {
unit *u = r->units;
r->units = u->next;
u->region = NULL;
uunhash(u);
free_unit(u);
free(u);
}
while (r->buildings) {
building *b = r->buildings;
assert(b->region == r);
r->buildings = b->next;
bunhash(b); /* must be done here, because remove_building does it, and wasn't called */
free_building(b);
}
while (r->ships) {
ship *s = r->ships;
assert(s->region == r);
r->ships = s->next;
sunhash(s);
free_ship(s);
}
free(r);
}
void free_regions(void)
{
memset(uidhash, 0, sizeof(uidhash));
while (deleted_regions) {
region *r = deleted_regions;
deleted_regions = r->next;
free_region(r);
}
while (regions) {
region *r = regions;
regions = r->next;
runhash(r);
free_region(r);
}
max_index = 0;
last = NULL;
}
/** creates a name for a region
* TODO: Make vowels XML-configurable and allow non-ascii characters again.
* - that will probably require a wchar_t * string to pick from.
*/
static char *makename(void)
{
int s, k, e, p = 0, x = 0;
size_t nk, ne, nv, ns;
static char name[16];
const char *kons = "bcdfghklmnprstvwz",
*handle_start = "bcdgtskpvfr",
*handle_end = "nlrdst",
*vowels = "aaaaaaaaaaaeeeeeeeeeeeeiiiiiiiiiiioooooooooooouuuuuuuuuuyy";
nk = strlen(kons);
ne = strlen(handle_end);
nv = strlen(vowels);
ns = strlen(handle_start);
for (s = rng_int() % 3 + 2; s > 0; s--) {
int v;
if (x > 0) {
k = rng_int() % (int)nk;
name[p] = kons[k];
p++;
}
else {
k = rng_int() % (int)ns;
name[p] = handle_start[k];
p++;
}
v = rng_int() % (int)nv;
name[p] = vowels[v];
p++;
if (rng_int() % 3 == 2 || s == 1) {
e = rng_int() % (int)ne;
name[p] = handle_end[e];
p++;
x = 1;
}
else
x = 0;
}
name[p] = '\0';
name[0] = (char)toupper(name[0]);
return name;
}
void setluxuries(region * r, const luxury_type * sale)
{
const luxury_type *ltype;
assert(r->land);
if (r->land->demands) {
freelist(r->land->demands);
r->land->demands = 0;
}
for (ltype = luxurytypes; ltype; ltype = ltype->next) {
struct demand *dmd = malloc(sizeof(struct demand));
if (!dmd) abort();
dmd->type = ltype;
if (ltype != sale)
dmd->value = 1 + rng_int() % 5;
else
dmd->value = 0;
dmd->next = r->land->demands;
r->land->demands = dmd;
}
}
int fix_demand(region * rd) {
luxury_type * ltype;
int maxluxuries = get_maxluxuries();
if (maxluxuries > 0) {
int sale = rng_int() % maxluxuries;
for (ltype = luxurytypes; sale != 0 && ltype; ltype = ltype->next) {
--sale;
}
setluxuries(rd, ltype);
return 0;
}
return -1;
}
void init_region(region *r)
{
static int changed;
static const terrain_type *t_plain;
const terrain_type * terrain = r->terrain;
int horses = 0, trees = 0;
if (terrain_changed(&changed)) {
t_plain = get_terrain(terrainnames[T_PLAIN]);
}
if (terrain->size>0) {
horses = rng_int() % (terrain->size / 50);
trees = terrain->size * (30 + rng_int() % 40) / 1000;
}
if (t_plain && terrain == t_plain) {
rsethorses(r, horses);
if (chance(0.4)) {
rsettrees(r, 2, trees);
}
}
else if (trees>0 && chance(0.2)) {
rsettrees(r, 2, trees);
}
else {
rsettrees(r, 2, 0);
}
rsettrees(r, 1, rtrees(r, 2) / 4);
rsettrees(r, 0, rtrees(r, 2) / 8);
if (!fval(r, RF_CHAOTIC)) {
int peasants;
peasants = (region_maxworkers(r) * (20 + dice(6, 10))) / 100;
if (peasants < 100) peasants = 100;
rsetpeasants(r, peasants);
rsetmoney(r, rpeasants(r) * ((wage(r, NULL, NULL,
INT_MAX) + 1) + rng_int() % 5));
}
}
void terraform_region(region * r, const terrain_type * terrain)
{
/* Resourcen, die nicht mehr vorkommen koennen, loeschen */
const terrain_type *oldterrain = r->terrain;
rawmaterial **lrm = &r->resources;
assert(terrain);
while (*lrm) {
rawmaterial *rm = *lrm;
const resource_type *rtype = NULL;
if (terrain->production != NULL) {
int i;
for (i = 0; terrain->production[i].type; ++i) {
if (rm->rtype == terrain->production[i].type) {
rtype = rm->rtype;
break;
}
}
}
if (rtype == NULL) {
*lrm = rm->next;
free(rm);
}
else {
lrm = &rm->next;
}
}
r->terrain = terrain;
terraform_resources(r);
if (!fval(terrain, LAND_REGION)) {
if (r->land) {
free_land(r->land);
r->land = NULL;
}
rsettrees(r, 0, 0);
rsettrees(r, 1, 0);
rsettrees(r, 2, 0);
rsethorses(r, 0);
rsetpeasants(r, 0);
rsetmoney(r, 0);
freset(r, RF_MALLORN);
return;
}
if (r->land) {
int d;
for (d = 0; d != MAXDIRECTIONS; ++d) {
rsetroad(r, d, 0);
}
}
else {
static struct surround {
struct surround *next;
const luxury_type *type;
int value;
} *trash = NULL, *nb = NULL;
const luxury_type *ltype = NULL;
direction_t d;
int mnr = 0;
r->land = calloc(1, sizeof(land_region));
if (!r->land) abort();
r->land->ownership = NULL;
region_set_morale(r, MORALE_DEFAULT, -1);
region_setname(r, makename());
fix_demand(r);
for (d = 0; d != MAXDIRECTIONS; ++d) {
region *nr = rconnect(r, d);
if (nr && nr->land) {
struct demand *sale = r->land->demands;
while (sale && sale->value != 0)
sale = sale->next;
if (sale) {
struct surround *sr = nb;
while (sr && sr->type != sale->type)
sr = sr->next;
if (!sr) {
if (trash) {
sr = trash;
trash = trash->next;
}
else {
sr = calloc(1, sizeof(struct surround));
if (!sr) abort();
}
sr->next = nb;
sr->type = sale->type;
sr->value = 1;
nb = sr;
}
else
sr->value++;
++mnr;
}
}
}
if (!nb) {
/* TODO: this is really lame */
int i = get_maxluxuries();
if (i > 0) {
i = rng_int() % i;
ltype = luxurytypes;
while (i--)
ltype = ltype->next;
}
}
else {
int i = rng_int() % mnr;
struct surround *srd = nb;
while (i > srd->value) {
i -= srd->value;
srd = srd->next;
}
if (srd->type)
setluxuries(r, srd->type);
while (srd->next != NULL)
srd = srd->next;
srd->next = trash;
trash = nb;
nb = NULL;
}
}
if (fval(terrain, LAND_REGION)) {
const item_type *itype = NULL;
if (r->terrain->herbs) {
int len = 0;
while (r->terrain->herbs[len])
++len;
if (len)
itype = r->terrain->herbs[rng_int() % len];
}
if (itype != NULL) {
rsetherbtype(r, itype);
rsetherbs(r, 50 + rng_int() % 31);
}
else {
rsetherbtype(r, NULL);
}
if (oldterrain == NULL || !fval(oldterrain, LAND_REGION)) {
if (rng_int() % 100 < 3)
fset(r, RF_MALLORN);
else
freset(r, RF_MALLORN);
}
if (oldterrain == NULL || terrain->size != oldterrain->size) {
init_region(r);
}
}
}
/** ENNO:
* ich denke, das das hier nicht sein sollte.
* statt dessen sollte ein attribut an der region sein, dass das erledigt,
* egal ob durch den spell oder anderes angelegt.
**/
#include "curse.h"
int max_production(const region * r)
{
/* muss rterrain(r) sein, nicht rterrain() wegen rekursion */
int p = r->terrain->size;
if (curse_active(get_curse(r->attribs, &ct_drought))) {
p /= 2;
}
return p;
}
void resolve_region(region *r)
{
resolve(RESOLVE_REGION | r->uid, r);
}
int read_region_reference(gamedata * data, region **rp)
{
struct storage * store = data->store;
int id = 0;
READ_INT(store, &id);
*rp = findregionbyid(id);
if (*rp == NULL) {
*rp = region_create(id);
}
return id;
}
void write_region_reference(const region * r, struct storage *store)
{
if (r) {
WRITE_INT(store, r->uid);
}
else {
WRITE_INT(store, 0);
}
}
struct message_list *r_getmessages(const struct region *r,
const struct faction *viewer)
{
struct individual_message *imsg = r->individual_messages;
while (imsg && (imsg)->viewer != viewer)
imsg = imsg->next;
if (imsg)
return imsg->msgs;
return NULL;
}
struct message *r_addmessage(struct region *r, const struct faction *viewer,
struct message *msg)
{
assert(r);
if (viewer) {
struct individual_message *imsg;
imsg = r->individual_messages;
while (imsg && imsg->viewer != viewer)
imsg = imsg->next;
if (imsg == NULL) {
imsg = malloc(sizeof(struct individual_message));
if (!imsg) abort();
imsg->next = r->individual_messages;
imsg->msgs = NULL;
r->individual_messages = imsg;
imsg->viewer = viewer;
}
return add_message(&imsg->msgs, msg);
}
return add_message(&r->msgs, msg);
}
struct faction *region_get_owner(const struct region *r)
{
if (r->land) {
if (rule_region_owners()) {
if (r->land->ownership) {
return r->land->ownership->owner;
}
}
else {
building *b = largestbuilding(r, cmp_castle_size, false);
unit * u = b ? building_owner(b) : NULL;
return u ? u->faction : NULL;
}
}
return NULL;
}
struct faction *region_get_last_owner(const struct region *r)
{
assert(rule_region_owners());
if (r->land && r->land->ownership) {
return r->land->ownership->last_owner;
}
return NULL;
}
struct alliance *region_get_alliance(const struct region *r)
{
assert(rule_region_owners());
if (r->land && r->land->ownership) {
region_owner *own = r->land->ownership;
return own->owner ? own->owner->alliance : (own->last_owner? own->last_owner->alliance : NULL);
}
return NULL;
}
void region_set_owner(struct region *r, struct faction *owner, int turn)
{
assert(rule_region_owners());
if (r->land) {
if (!r->land->ownership) {
region_owner *ro = malloc(sizeof(region_owner));
if (!ro) abort();
assert(region_get_morale(r) == MORALE_DEFAULT);
ro->owner = NULL;
ro->last_owner = NULL;
ro->flags = 0;
r->land->ownership = ro;
}
r->land->ownership->since_turn = turn;
r->land->ownership->morale_turn = turn;
assert(r->land->ownership->owner != owner);
r->land->ownership->last_owner = r->land->ownership->owner;
r->land->ownership->owner = owner;
}
}
faction *update_owners(region * r)
{
faction *f = NULL;
assert(rule_region_owners());
if (r->land) {
building *bowner = largestbuilding(r, cmp_current_owner, false);
building *blargest = largestbuilding(r, cmp_taxes, false);
if (blargest) {
if (!bowner || bowner->size < blargest->size) {
/* region owners update? */
unit *new_owner = building_owner(blargest);
f = region_get_owner(r);
if (new_owner == NULL) {
if (f) {
region_set_owner(r, NULL, turn);
f = NULL;
}
}
else if (new_owner->faction != f) {
if (!r->land->ownership) {
/* there has never been a prior owner */
region_set_morale(r, MORALE_DEFAULT, turn);
}
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 = region_get_morale(r) - MORALE_TRANSFER;
if (morale < 0) morale = 0;
region_set_morale(r, morale, turn);
}
else {
region_set_morale(r, MORALE_TAKEOVER, turn);
}
}
region_set_owner(r, new_owner->faction, turn);
f = new_owner->faction;
}
}
}
else if (r->land->ownership && r->land->ownership->owner) {
region_set_owner(r, NULL, turn);
f = NULL;
}
}
return f;
}
void region_setinfo(struct region *r, const char *info)
{
assert(r->land);
free(r->land->display);
r->land->display = (info && info[0]) ? str_strdup(info) : 0;
}
const char *region_getinfo(const region * r)
{
return (r->land && r->land->display) ? r->land->display : "";
}
void region_setname(struct region *r, const char *name)
{
if (r->land) {
free(r->land->name);
r->land->name = name ? str_strdup(name) : 0;
}
}
const char *region_getname(const region * r)
{
if (r->land && r->land->name) {
return r->land->name;
}
return "";
}
int region_get_morale(const region * r)
{
if (r->land) {
assert(r->land->morale <= MORALE_MAX);
return r->land->morale;
}
return -1;
}
void region_set_morale(region * r, int morale, int turn)
{
if (r->land) {
r->land->morale = (unsigned short)morale;
if (turn >= 0 && r->land->ownership) {
r->land->ownership->morale_turn = turn;
}
assert(r->land->morale <= MORALE_MAX);
}
}
void get_neighbours(const region * r, region ** list)
{
int dir;
for (dir = 0; dir != MAXDIRECTIONS; ++dir) {
list[dir] = rconnect(r, (direction_t)dir);
}
}
int owner_change(const region * r)
{
if (r->land && r->land->ownership) {
return r->land->ownership->since_turn;
}
return INT_MIN;
}
bool is_mourning(const region * r, int in_turn)
{
int change = owner_change(r);
return (change == in_turn - 1 && r->land &&
r->land->ownership->last_owner && r->land->ownership->owner &&
r->land->ownership->last_owner != r->land->ownership->owner);
}