server/src/kernel/config.c
Enno Rehling b21cb8f5c7 Simple test for rules.give (there are many more, can_give deserves a test to itself).
The more tests I write, the more I hate the infrastructure for them.
2014-07-05 23:06:51 -07:00

2892 lines
63 KiB
C
Raw Blame History

/*
Copyright (c) 1998-2010, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
#include <platform.h>
#include <kernel/config.h>
/* attributes includes */
#include <attributes/reduceproduction.h>
#include <attributes/gm.h>
/* kernel includes */
#include "alliance.h"
#include "ally.h"
#include "alchemy.h"
#include "battle.h"
#include "connection.h"
#include "building.h"
#include "calendar.h"
#include "curse.h"
#include "direction.h"
#include "faction.h"
#include "group.h"
#include "item.h"
#include "keyword.h"
#include "magic.h"
#include "messages.h"
#include "move.h"
#include "names.h"
#include "objtypes.h"
#include "order.h"
#include "plane.h"
#include "pool.h"
#include "race.h"
#include "reports.h"
#include "region.h"
#include "save.h"
#include "ship.h"
#include "skill.h"
#include "terrain.h"
#include "unit.h"
#include <kernel/spell.h>
#include <kernel/spellbook.h>
/* util includes */
#include <util/attrib.h>
#include <util/base36.h>
#include <util/bsdstring.h>
#include <util/crmessage.h>
#include <util/event.h>
#include <util/language.h>
#include <util/filereader.h>
#include <util/functions.h>
#include <util/log.h>
#include <util/lists.h>
#include <util/parser.h>
#include <quicklist.h>
#include <util/rand.h>
#include <util/rng.h>
#include <util/translation.h>
#include <util/unicode.h>
#include <util/umlaut.h>
#include <util/xml.h>
#ifdef USE_LIBXML2
/* libxml includes */
#include <libxml/tree.h>
#include <libxml/xpath.h>
#endif
/* external libraries */
#include <iniparser.h>
#include <critbit.h>
/* libc includes */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <math.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
struct settings global = {
"Eressea", /* gamename */
};
bool lomem = false;
FILE *logfile;
FILE *updatelog;
bool battledebug = false;
int turn = -1;
int NewbieImmunity(void)
{
static int value = -1;
static int gamecookie = -1;
if (value < 0 || gamecookie != global.cookie) {
gamecookie = global.cookie;
value = get_param_int(global.parameters, "NewbieImmunity", 0);
}
return value;
}
bool IsImmune(const faction * f)
{
return !fval(f, FFL_NPC) && f->age < NewbieImmunity();
}
static int MaxAge(void)
{
static int value = -1;
static int gamecookie = -1;
if (value < 0 || gamecookie != global.cookie) {
gamecookie = global.cookie;
value = get_param_int(global.parameters, "MaxAge", 0);
}
return value;
}
static int ally_flag(const char *s, int help_mask)
{
if ((help_mask & HELP_MONEY) && strcmp(s, "money") == 0)
return HELP_MONEY;
if ((help_mask & HELP_FIGHT) && strcmp(s, "fight") == 0)
return HELP_FIGHT;
if ((help_mask & HELP_GIVE) && strcmp(s, "give") == 0)
return HELP_GIVE;
if ((help_mask & HELP_GUARD) && strcmp(s, "guard") == 0)
return HELP_GUARD;
if ((help_mask & HELP_FSTEALTH) && strcmp(s, "stealth") == 0)
return HELP_FSTEALTH;
if ((help_mask & HELP_TRAVEL) && strcmp(s, "travel") == 0)
return HELP_TRAVEL;
return 0;
}
bool ExpensiveMigrants(void)
{
static int value = -1;
static int gamecookie = -1;
if (value < 0 || gamecookie != global.cookie) {
gamecookie = global.cookie;
value = get_param_int(global.parameters, "study.expensivemigrants", 0);
}
return value;
}
/** Specifies automatic alliance modes.
* If this returns a value then the bits set are immutable between alliance
* partners (faction::alliance) and cannot be changed with the HELP command.
*/
int AllianceAuto(void)
{
static int value = -1;
static int gamecookie = -1;
if (value < 0 || gamecookie != global.cookie) {
const char *str = get_param(global.parameters, "alliance.auto");
gamecookie = global.cookie;
value = 0;
if (str != NULL) {
char *sstr = _strdup(str);
char *tok = strtok(sstr, " ");
while (tok) {
value |= ally_flag(tok, -1);
tok = strtok(NULL, " ");
}
free(sstr);
}
}
return value & HelpMask();
}
/** Limits the available help modes
* The bitfield returned by this function specifies the available help modes
* in this game (so you can, for example, disable HELP GIVE globally).
* Disabling a status will disable the command sequence entirely (order parsing
* uses this function).
*/
int HelpMask(void)
{
static int rule = -1;
static int gamecookie = -1;
if (rule < 0 || gamecookie != global.cookie) {
const char *str = get_param(global.parameters, "rules.help.mask");
gamecookie = global.cookie;
rule = 0;
if (str != NULL) {
char *sstr = _strdup(str);
char *tok = strtok(sstr, " ");
while (tok) {
rule |= ally_flag(tok, -1);
tok = strtok(NULL, " ");
}
free(sstr);
} else {
rule = HELP_ALL;
}
}
return rule;
}
int AllianceRestricted(void)
{
static int rule = -1;
static int gamecookie = -1;
if (rule < 0 || gamecookie != global.cookie) {
const char *str = get_param(global.parameters, "alliance.restricted");
gamecookie = global.cookie;
rule = 0;
if (str != NULL) {
char *sstr = _strdup(str);
char *tok = strtok(sstr, " ");
while (tok) {
rule |= ally_flag(tok, -1);
tok = strtok(NULL, " ");
}
free(sstr);
}
rule &= HelpMask();
}
return rule;
}
int LongHunger(const struct unit *u)
{
static int gamecookie = -1;
static int rule = -1;
if (u != NULL) {
if (!fval(u, UFL_HUNGER))
return false;
#ifdef NEW_DAEMONHUNGER_RULE
if (u_race(u) == get_race(RC_DAEMON))
return false;
#endif
}
if (rule < 0 || gamecookie != global.cookie) {
gamecookie = global.cookie;
rule = get_param_int(global.parameters, "hunger.long", 0);
}
return rule;
}
int SkillCap(skill_t sk)
{
static int gamecookie = -1;
static int rule = -1;
if (sk == SK_MAGIC)
return 0; /* no caps on magic */
if (rule < 0 || gamecookie != global.cookie) {
gamecookie = global.cookie;
rule = get_param_int(global.parameters, "skill.maxlevel", 0);
}
return rule;
}
int NMRTimeout(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
gamecookie = global.cookie;
rule = get_param_int(global.parameters, "nmr.timeout", 0);
}
return rule;
}
race_t old_race(const struct race * rc)
{
race_t i;
for (i = 0; i != MAXRACES; ++i) {
if (get_race(i) == rc) return i;
}
return NORACE;
}
helpmode helpmodes[] = {
{"all", HELP_ALL}
,
{"money", HELP_MONEY}
,
{"fight", HELP_FIGHT}
,
{"observe", HELP_OBSERVE}
,
{"give", HELP_GIVE}
,
{"guard", HELP_GUARD}
,
{"stealth", HELP_FSTEALTH}
,
{"travel", HELP_TRAVEL}
,
{NULL, 0}
};
/** Returns the English name of the race, which is what the database uses.
*/
const char *dbrace(const struct race *rc)
{
static char zText[32];
char *zPtr = zText;
/* the english names are all in ASCII, so we don't need to worry about UTF8 */
strcpy(zText, (const char *)LOC(get_locale("en"), rc_name(rc, 0)));
while (*zPtr) {
*zPtr = (char)(toupper(*zPtr));
++zPtr;
}
return zText;
}
const char *parameters[MAXPARAMS] = {
"LOCALE",
"ALLES",
"JEDEM",
"BAUERN",
"BURG",
"EINHEIT",
"PRIVAT",
"HINTEN",
"KOMMANDO",
"KRAEUTER",
"NICHT",
"NAECHSTER",
"PARTEI",
"ERESSEA",
"PERSONEN",
"REGION",
"SCHIFF",
"SILBER",
"STRASSEN",
"TEMPORAERE",
"FLIEHE",
"GEBAEUDE",
"GIB", /* F<>r HELFE */
"KAEMPFE",
"DURCHREISE",
"BEWACHE",
"ZAUBER",
"PAUSE",
"VORNE",
"AGGRESSIV",
"DEFENSIV",
"STUFE",
"HELFE",
"FREMDES",
"AURA",
"HINTER",
"VOR",
"ANZAHL",
"GEGENSTAENDE",
"TRAENKE",
"GRUPPE",
"PARTEITARNUNG",
"BAEUME",
"ALLIANZ"
};
const char *report_options[MAX_MSG] = {
"Kampf",
"Ereignisse",
"Bewegung",
"Einkommen",
"Handel",
"Produktion",
"Orkvermehrung",
"Zauber",
"",
""
};
const char *message_levels[ML_MAX] = {
"Wichtig",
"Debug",
"Fehler",
"Warnungen",
"Infos"
};
const char *options[MAXOPTIONS] = {
"AUSWERTUNG",
"COMPUTER",
"ZUGVORLAGE",
NULL,
"STATISTIK",
"DEBUG",
"ZIPPED",
"ZEITUNG", /* Option hat Sonderbehandlung! */
NULL,
"ADRESSEN",
"BZIP2",
"PUNKTE",
"SHOWSKCHANGE"
};
static void init_maxmagicians(struct attrib *a)
{
a->data.i = MAXMAGICIANS;
}
static attrib_type at_maxmagicians = {
"maxmagicians",
init_maxmagicians,
NULL,
NULL,
a_writeint,
a_readint,
ATF_UNIQUE
};
int max_magicians(const faction * f)
{
int m =
get_param_int(global.parameters, "rules.maxskills.magic", MAXMAGICIANS);
attrib *a;
if ((a = a_find(f->attribs, &at_maxmagicians)) != NULL) {
m = a->data.i;
}
if (f->race == get_race(RC_ELF))
++m;
return m;
}
static void init_npcfaction(struct attrib *a)
{
a->data.i = 1;
}
static attrib_type at_npcfaction = {
"npcfaction",
init_npcfaction,
NULL,
NULL,
a_writeint,
a_readint,
ATF_UNIQUE
};
int verbosity = 1;
FILE *debug;
static int ShipSpeedBonus(const unit * u)
{
static int level = -1;
if (level == -1) {
level =
get_param_int(global.parameters, "movement.shipspeed.skillbonus", 0);
}
if (level > 0) {
ship *sh = u->ship;
int skl = effskill(u, SK_SAILING);
int minsk = (sh->type->cptskill + 1) / 2;
return (skl - minsk) / level;
}
return 0;
}
int shipspeed(const ship * sh, const unit * u)
{
double k = sh->type->range;
static const curse_type *stormwind_ct, *nodrift_ct;
static bool init;
attrib *a;
curse *c;
if (!init) {
init = true;
stormwind_ct = ct_find("stormwind");
nodrift_ct = ct_find("nodrift");
}
assert(u->ship == sh);
assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */
if (sh->size != sh->type->construction->maxsize)
return 0;
if (curse_active(get_curse(sh->attribs, stormwind_ct)))
k *= 2;
if (curse_active(get_curse(sh->attribs, nodrift_ct)))
k += 1;
if (u->faction->race == u_race(u)) {
/* race bonus for this faction? */
if (fval(u_race(u), RCF_SHIPSPEED)) {
k += 1;
}
}
k += ShipSpeedBonus(u);
a = a_find(sh->attribs, &at_speedup);
while (a != NULL && a->type == &at_speedup) {
k += a->data.sa[0];
a = a->next;
}
c = get_curse(sh->attribs, ct_find("shipspeedup"));
while (c) {
k += curse_geteffect(c);
c = c->nexthash;
}
#ifdef SHIPSPEED
k *= SHIPSPEED;
#endif
#ifdef SHIPDAMAGE
if (sh->damage)
k =
(k * (sh->size * DAMAGE_SCALE - sh->damage) + sh->size * DAMAGE_SCALE -
1) / (sh->size * DAMAGE_SCALE);
#endif
return (int)k;
}
#define FMAXHASH 2039
faction *factionhash[FMAXHASH];
void fhash(faction * f)
{
int index = f->no % FMAXHASH;
f->nexthash = factionhash[index];
factionhash[index] = f;
}
void funhash(faction * f)
{
int index = f->no % FMAXHASH;
faction **fp = factionhash + index;
while (*fp && (*fp) != f)
fp = &(*fp)->nexthash;
*fp = f->nexthash;
}
static faction *ffindhash(int no)
{
int index = no % FMAXHASH;
faction *f = factionhash[index];
while (f && f->no != no)
f = f->nexthash;
return f;
}
/* ----------------------------------------------------------------------- */
void verify_data(void)
{
#ifndef NDEBUG
int lf = -1;
faction *f;
unit *u;
int mage, alchemist;
if (verbosity >= 1)
puts(" - <20>berpr<70>fe Daten auf Korrektheit...");
for (f = factions; f; f = f->next) {
mage = 0;
alchemist = 0;
for (u = f->units; u; u = u->nextF) {
if (eff_skill(u, SK_MAGIC, u->region)) {
mage += u->number;
}
if (eff_skill(u, SK_ALCHEMY, u->region))
alchemist += u->number;
if (u->number > UNIT_MAXSIZE) {
if (lf != f->no) {
lf = f->no;
log_printf(stdout, "Partei %s:\n", factionid(f));
}
log_warning("Einheit %s hat %d Personen\n", unitid(u), u->number);
}
}
if (f->no != 0 && ((mage > 3 && f->race != get_race(RC_ELF)) || mage > 4))
log_error("Partei %s hat %d Magier.\n", factionid(f), mage);
if (alchemist > 3)
log_error("Partei %s hat %d Alchemisten.\n", factionid(f), alchemist);
}
#endif
}
int distribute(int old, int new_value, int n)
{
int i;
int t;
assert(new_value <= old);
if (old == 0)
return 0;
t = (n / old) * new_value;
for (i = (n % old); i; i--)
if (rng_int() % old < new_value)
t++;
return t;
}
int change_hitpoints(unit * u, int value)
{
int hp = u->hp;
hp += value;
/* Jede Person ben<65>tigt mindestens 1 HP */
if (hp < u->number) {
if (hp < 0) { /* Einheit tot */
hp = 0;
}
scale_number(u, hp);
}
u->hp = hp;
return hp;
}
unsigned int atoip(const char *s)
{
int n;
n = atoi(s);
if (n < 0)
n = 0;
return n;
}
region *findunitregion(const unit * su)
{
#ifndef SLOW_REGION
return su->region;
#else
region *r;
const unit *u;
for (r = regions; r; r = r->next) {
for (u = r->units; u; u = u->next) {
if (su == u) {
return r;
}
}
}
/* This should never happen */
assert(!"Die unit wurde nicht gefunden");
return (region *) NULL;
#endif
}
int eff_stealth(const unit * u, const region * r)
{
int e = 0;
/* Auf Schiffen keine Tarnung! */
if (!u->ship && skill_enabled(SK_STEALTH)) {
e = eff_skill(u, SK_STEALTH, r);
if (fval(u, UFL_STEALTH)) {
int es = u_geteffstealth(u);
if (es >= 0 && es < e)
return es;
}
}
return e;
}
bool unit_has_cursed_item(unit * u)
{
item *itm = u->items;
while (itm) {
if (fval(itm->type, ITF_CURSED) && itm->number > 0)
return true;
itm = itm->next;
}
return false;
}
static void init_gms(void)
{
faction *f;
for (f = factions; f; f = f->next) {
const attrib *a = a_findc(f->attribs, &at_gm);
if (a != NULL)
fset(f, FFL_GM);
}
}
static int
autoalliance(const plane * pl, const faction * sf, const faction * f2)
{
static bool init = false;
if (!init) {
init_gms();
init = true;
}
if (pl && (pl->flags & PFL_FRIENDLY))
return HELP_ALL;
/* if f2 is a gm in this plane, everyone has an auto-help to it */
if (fval(f2, FFL_GM)) {
attrib *a = a_find(f2->attribs, &at_gm);
while (a) {
const plane *p = (const plane *)a->data.v;
if (p == pl)
return HELP_ALL;
a = a->next;
}
}
if (f_get_alliance(sf) != NULL && AllianceAuto()) {
if (sf->alliance == f2->alliance)
return AllianceAuto();
}
return 0;
}
static int ally_mode(const ally * sf, int mode)
{
if (sf == NULL)
return 0;
return sf->status & mode;
}
int
alliedgroup(const struct plane *pl, const struct faction *f,
const struct faction *f2, const struct ally *sf, int mode)
{
while (sf && sf->faction != f2)
sf = sf->next;
if (sf == NULL) {
mode = mode & autoalliance(pl, f, f2);
}
mode = ally_mode(sf, mode) | (mode & autoalliance(pl, f, f2));
if (AllianceRestricted()) {
if (a_findc(f->attribs, &at_npcfaction)) {
return mode;
}
if (a_findc(f2->attribs, &at_npcfaction)) {
return mode;
}
if (f->alliance != f2->alliance) {
mode &= ~AllianceRestricted();
}
}
return mode;
}
int
alliedfaction(const struct plane *pl, const struct faction *f,
const struct faction *f2, int mode)
{
return alliedgroup(pl, f, f2, f->allies, mode);
}
/* Die Gruppe von Einheit u hat helfe zu f2 gesetzt. */
int alliedunit(const unit * u, const faction * f2, int mode)
{
ally *sf;
int automode;
assert(u->region); /* the unit should be in a region, but it's possible that u->number==0 (TEMP units) */
if (u->faction == f2)
return mode;
if (u->faction != NULL && f2 != NULL) {
plane *pl;
if (mode & HELP_FIGHT) {
if ((u->flags & UFL_DEFENDER) || (u->faction->flags & FFL_DEFENDER)) {
faction *owner = region_get_owner(u->region);
/* helps the owner of the region */
if (owner == f2) {
return HELP_FIGHT;
}
}
}
pl = rplane(u->region);
automode = mode & autoalliance(pl, u->faction, f2);
if (pl != NULL && (pl->flags & PFL_NOALLIANCES))
mode = (mode & automode) | (mode & HELP_GIVE);
sf = u->faction->allies;
if (fval(u, UFL_GROUP)) {
const attrib *a = a_findc(u->attribs, &at_group);
if (a != NULL)
sf = ((group *) a->data.v)->allies;
}
return alliedgroup(pl, u->faction, f2, sf, mode);
}
return 0;
}
bool
seefaction(const faction * f, const region * r, const unit * u, int modifier)
{
if (((f == u->faction) || !fval(u, UFL_ANON_FACTION))
&& cansee(f, r, u, modifier))
return true;
return false;
}
bool
cansee(const faction * f, const region * r, const unit * u, int modifier)
/* r kann != u->region sein, wenn es um durchreisen geht */
/* und es muss niemand aus f in der region sein, wenn sie vom Turm
* erblickt wird */
{
int stealth, rings;
unit *u2 = r->units;
if (u->faction == f || omniscient(f)) {
return true;
} else if (fval(u_race(u), RCF_INVISIBLE)) {
return false;
} else if (u->number == 0) {
attrib *a = a_find(u->attribs, &at_creator);
if (a) { /* u is an empty temporary unit. In this special case
we look at the creating unit. */
u = (unit *) a->data.v;
} else {
return false;
}
}
if (leftship(u))
return true;
while (u2 && u2->faction != f)
u2 = u2->next;
if (u2 == NULL)
return false;
/* simple visibility, just gotta have a unit in the region to see 'em */
if (is_guard(u, GUARD_ALL) != 0 || usiege(u) || u->building || u->ship) {
return true;
}
rings = invisible(u, NULL);
stealth = eff_stealth(u, r) - modifier;
while (u2) {
if (rings < u->number || invisible(u, u2) < u->number) {
if (skill_enabled(SK_PERCEPTION)) {
int observation = eff_skill(u2, SK_PERCEPTION, r);
if (observation >= stealth) {
return true;
}
} else {
return true;
}
}
/* find next unit in our faction */
do {
u2 = u2->next;
} while (u2 && u2->faction != f);
}
return false;
}
bool cansee_unit(const unit * u, const unit * target, int modifier)
/* target->region kann != u->region sein, wenn es um durchreisen geht */
{
if (fval(u_race(target), RCF_INVISIBLE) || target->number == 0)
return false;
else if (target->faction == u->faction)
return true;
else {
int n, rings, o;
if (is_guard(target, GUARD_ALL) != 0 || usiege(target) || target->building
|| target->ship) {
return true;
}
n = eff_stealth(target, target->region) - modifier;
rings = invisible(target, NULL);
if (rings == 0 && n <= 0) {
return true;
}
if (rings && invisible(target, u) >= target->number) {
return false;
}
if (skill_enabled(SK_PERCEPTION)) {
o = eff_skill(u, SK_PERCEPTION, target->region);
if (o >= n) {
return true;
}
} else {
return true;
}
}
return false;
}
bool
cansee_durchgezogen(const faction * f, const region * r, const unit * u,
int modifier)
/* r kann != u->region sein, wenn es um durchreisen geht */
/* und es muss niemand aus f in der region sein, wenn sie vom Turm
* erblickt wird */
{
int n;
unit *u2;
if (fval(u_race(u), RCF_INVISIBLE) || u->number == 0)
return false;
else if (u->faction == f)
return true;
else {
int rings;
if (is_guard(u, GUARD_ALL) != 0 || usiege(u) || u->building || u->ship) {
return true;
}
n = eff_stealth(u, r) - modifier;
rings = invisible(u, NULL);
if (rings == 0 && n <= 0) {
return true;
}
for (u2 = r->units; u2; u2 = u2->next) {
if (u2->faction == f) {
int o;
if (rings && invisible(u, u2) >= u->number)
continue;
o = eff_skill(u2, SK_PERCEPTION, r);
if (o >= n) {
return true;
}
}
}
}
return false;
}
#ifndef NDEBUG
const char *strcheck(const char *s, size_t maxlen)
{
static char buffer[16 * 1024];
if (strlen(s) > maxlen) {
assert(maxlen < 16 * 1024);
log_warning("[strcheck] String wurde auf %d Zeichen verk<72>rzt:\n%s\n", (int)maxlen, s);
strlcpy(buffer, s, maxlen);
return buffer;
}
return s;
}
#endif
static attrib_type at_lighthouse = {
"lighthouse"
/* Rest ist NULL; tempor<6F>res, nicht alterndes Attribut */
};
/* update_lighthouse: call this function whenever the size of a lighthouse changes
* it adds temporary markers to the surrounding regions.
* The existence of markers says nothing about the quality of the observer in
* the lighthouse, for this may change more frequently.
*/
void update_lighthouse(building * lh)
{
const struct building_type *bt_lighthouse = bt_find("lighthouse");
if (bt_lighthouse && lh->type == bt_lighthouse) {
region *r = lh->region;
int d = (int)log10(lh->size) + 1;
int x;
if (lh->size > 0) {
r->flags |= RF_LIGHTHOUSE;
}
for (x = -d; x <= d; ++x) {
int y;
for (y = -d; y <= d; ++y) {
attrib *a;
region *r2;
int px = r->x + x, py = r->y + y;
pnormalize(&px, &py, rplane(r));
r2 = findregion(px, py);
if (!r2 || !fval(r2->terrain, SEA_REGION))
continue;
if (distance(r, r2) > d)
continue;
a = a_find(r2->attribs, &at_lighthouse);
while (a && a->type == &at_lighthouse) {
building *b = (building *) a->data.v;
if (b == lh)
break;
a = a->next;
}
if (!a) {
a = a_add(&r2->attribs, a_new(&at_lighthouse));
a->data.v = (void *)lh;
}
}
}
}
}
int count_faction(const faction * f, int flags)
{
unit *u;
int n = 0;
for (u = f->units; u; u = u->nextF) {
const race *rc = u_race(u);
int x = (flags&COUNT_UNITS) ? 1 : u->number;
if (f->race!=rc) {
if (!playerrace(rc)) {
if (flags&COUNT_MONSTERS) {
n+=x;
}
} else if (flags&COUNT_MIGRANTS) {
if (!is_cursed(u->attribs, C_SLAVE, 0)) {
n+=x;
}
}
} else if (flags&COUNT_DEFAULT) {
n+=x;
}
}
return n;
}
int count_units(const faction * f)
{
return count_faction(f, COUNT_ALL|COUNT_UNITS);
}
int count_all(const faction * f)
{
return count_faction(f, COUNT_ALL);
}
int count_migrants(const faction * f)
{
return count_faction(f, COUNT_MIGRANTS);
}
int count_maxmigrants(const faction * f)
{
static int migrants = -1;
if (migrants < 0) {
migrants = get_param_int(global.parameters, "rules.migrants", INT_MAX);
}
if (migrants == INT_MAX) {
int x = 0;
if (f->race == get_race(RC_HUMAN)) {
int nsize = count_all(f);
if (nsize > 0) {
x = (int)(log10(nsize / 50.0) * 20);
if (x < 0)
x = 0;
}
}
return x;
}
return migrants;
}
void
parse(keyword_t kword, int (*dofun) (unit *, struct order *), bool thisorder)
{
region *r;
for (r = regions; r; r = r->next) {
unit **up = &r->units;
while (*up) {
unit *u = *up;
order **ordp = &u->orders;
if (thisorder)
ordp = &u->thisorder;
while (*ordp) {
order *ord = *ordp;
if (getkeyword(ord) == kword) {
if (dofun(u, ord) != 0)
break;
if (u->orders == NULL)
break;
}
if (thisorder)
break;
if (*ordp == ord)
ordp = &ord->next;
}
if (*up == u)
up = &u->next;
}
}
}
const char *igetstrtoken(const char *initstr)
{
if (initstr != NULL) {
init_tokens_str(initstr, NULL);
}
return getstrtoken();
}
unsigned int getuint(void)
{
return atoip((const char *)getstrtoken());
}
int getint(void)
{
return atoi((const char *)getstrtoken());
}
const struct race *findrace(const char *s, const struct locale *lang)
{
void **tokens = get_translations(lang, UT_RACES);
variant token;
assert(lang);
if (tokens && findtoken(*tokens, s, &token) == E_TOK_SUCCESS) {
return (const struct race *)token.v;
}
return NULL;
}
int findoption(const char *s, const struct locale *lang)
{
void **tokens = get_translations(lang, UT_OPTIONS);
variant token;
if (findtoken(*tokens, s, &token) == E_TOK_SUCCESS) {
return (direction_t) token.i;
}
return NODIRECTION;
}
param_t findparam(const char *s, const struct locale * lang)
{
param_t result = NOPARAM;
char buffer[64];
char * str = transliterate(buffer, sizeof(buffer)-sizeof(int), s);
if (str && *str) {
int i;
const void * match;
void **tokens = get_translations(lang, UT_PARAMS);
critbit_tree *cb = (critbit_tree *)*tokens;
if (!cb) {
log_error_n("no parameters defined in locale %s", locale_name(lang));
}
else if (cb_find_prefix(cb, str, strlen(str), &match, 1, 0)) {
cb_get_kv(match, &i, sizeof(int));
result = (param_t)i;
}
}
return result;
}
param_t findparam_ex(const char *s, const struct locale * lang)
{
param_t result = findparam(s, lang);
if (result==NOPARAM) {
const building_type *btype = findbuildingtype(s, lang);
if (btype != NULL)
return P_GEBAEUDE;
}
return (result == P_BUILDING) ? P_GEBAEUDE : result;
}
bool isparam(const char *s, const struct locale * lang, param_t param)
{
if (s[0]>'@') {
param_t p = (param==P_GEBAEUDE) ? findparam_ex(s, lang) : findparam(s, lang);
return p==param;
}
return false;
}
param_t getparam(const struct locale * lang)
{
return findparam(getstrtoken(), lang);
}
faction *findfaction(int n)
{
faction *f = ffindhash(n);
return f;
}
faction *getfaction(void)
{
return findfaction(getid());
}
unit *findunitr(const region * r, int n)
{
unit *u;
/* findunit regional! */
for (u = r->units; u; u = u->next)
if (u->no == n)
return u;
return 0;
}
unit *findunit(int n)
{
if (n <= 0) {
return NULL;
}
return ufindhash(n);
}
unit *findunitg(int n, const region * hint)
{
/* Abfangen von Syntaxfehlern. */
if (n <= 0)
return NULL;
/* findunit global! */
hint = 0;
return ufindhash(n);
}
unit *getnewunit(const region * r, const faction * f)
{
int n;
n = getid();
return findnewunit(r, f, n);
}
static int read_newunitid(const faction * f, const region * r)
{
int n;
unit *u2;
n = getid();
if (n == 0)
return -1;
u2 = findnewunit(r, f, n);
if (u2)
return u2->no;
return -1;
}
int read_unitid(const faction * f, const region * r)
{
const char *s = getstrtoken();
/* Da s nun nur einen string enthaelt, suchen wir ihn direkt in der
* paramliste. machen wir das nicht, dann wird getnewunit in s nach der
* nummer suchen, doch dort steht bei temp-units nur "temp" drinnen! */
if (!s || *s == 0) {
return -1;
}
if (isparam(s, f->locale, P_TEMP)) {
return read_newunitid(f, r);
}
return atoi36((const char *)s);
}
/* exported symbol */
bool getunitpeasants;
unit *getunitg(const region * r, const faction * f)
{
int n = read_unitid(f, r);
if (n == 0) {
getunitpeasants = 1;
return NULL;
}
getunitpeasants = 0;
if (n < 0)
return 0;
return findunit(n);
}
unit *getunit(const region * r, const faction * f)
{
int n = read_unitid(f, r);
unit *u2;
if (n == 0) {
getunitpeasants = 1;
return NULL;
}
getunitpeasants = 0;
if (n < 0)
return 0;
u2 = findunit(n);
if (u2 != NULL && u2->region == r) {
/* there used to be a 'u2->flags & UFL_ISNEW || u2->number>0' condition
* here, but it got removed because of a bug that made units disappear:
* http://eressea.upb.de/mantis/bug_view_page.php?bug_id=0000172
*/
return u2;
}
return NULL;
}
/* - String Listen --------------------------------------------- */
void addstrlist(strlist ** SP, const char *s)
{
strlist *slist = malloc(sizeof(strlist));
slist->next = NULL;
slist->s = _strdup(s);
addlist(SP, slist);
}
void freestrlist(strlist * s)
{
strlist *q, *p = s;
while (p) {
q = p->next;
free(p->s);
free(p);
p = q;
}
}
/* - Namen der Strukturen -------------------------------------- */
typedef char name[OBJECTIDSIZE + 1];
static name idbuf[8];
static int nextbuf = 0;
char *estring_i(char *ibuf)
{
char *p = ibuf;
while (*p) {
if (isxspace(*(unsigned *)p) == ' ') {
*p = '~';
}
++p;
}
return ibuf;
}
char *estring(const char *s)
{
char *ibuf = idbuf[(++nextbuf) % 8];
strlcpy(ibuf, s, sizeof(name));
return estring_i(ibuf);
}
char *cstring_i(char *ibuf)
{
char *p = ibuf;
while (*p) {
if (*p == '~') {
*p = ' ';
}
++p;
}
return ibuf;
}
char *cstring(const char *s)
{
char *ibuf = idbuf[(++nextbuf) % 8];
strlcpy(ibuf, s, sizeof(name));
return cstring_i(ibuf);
}
building *largestbuilding(const region * r, cmp_building_cb cmp_gt,
bool imaginary)
{
building *b, *best = NULL;
for (b = rbuildings(r); b; b = b->next) {
if (cmp_gt(b, best) <= 0)
continue;
if (!imaginary) {
const attrib *a = a_find(b->attribs, &at_icastle);
if (a)
continue;
}
best = b;
}
return best;
}
char *write_unitname(const unit * u, char *buffer, size_t size)
{
slprintf(buffer, size, "%s (%s)", (const char *)u->name, itoa36(u->no));
buffer[size - 1] = 0;
return buffer;
}
const char *unitname(const unit * u)
{
char *ubuf = idbuf[(++nextbuf) % 8];
return write_unitname(u, ubuf, sizeof(name));
}
/* -- Erschaffung neuer Einheiten ------------------------------ */
extern faction *dfindhash(int i);
static const char *forbidden[] = { "t", "te", "tem", "temp", NULL };
int forbiddenid(int id)
{
static int *forbid = NULL;
static size_t len;
size_t i;
if (id <= 0)
return 1;
if (!forbid) {
while (forbidden[len])
++len;
forbid = calloc(len, sizeof(int));
for (i = 0; i != len; ++i) {
forbid[i] = strtol(forbidden[i], NULL, 36);
}
}
for (i = 0; i != len; ++i)
if (id == forbid[i])
return 1;
return 0;
}
/* ID's f<>r Einheiten und Zauber */
int newunitid(void)
{
int random_unit_no;
int start_random_no;
random_unit_no = 1 + (rng_int() % MAX_UNIT_NR);
start_random_no = random_unit_no;
while (ufindhash(random_unit_no) || dfindhash(random_unit_no)
|| cfindhash(random_unit_no)
|| forbiddenid(random_unit_no)) {
random_unit_no++;
if (random_unit_no == MAX_UNIT_NR + 1) {
random_unit_no = 1;
}
if (random_unit_no == start_random_no) {
random_unit_no = (int)MAX_UNIT_NR + 1;
}
}
return random_unit_no;
}
int newcontainerid(void)
{
int random_no;
int start_random_no;
random_no = 1 + (rng_int() % MAX_CONTAINER_NR);
start_random_no = random_no;
while (findship(random_no) || findbuilding(random_no)) {
random_no++;
if (random_no == MAX_CONTAINER_NR + 1) {
random_no = 1;
}
if (random_no == start_random_no) {
random_no = (int)MAX_CONTAINER_NR + 1;
}
}
return random_no;
}
unit *createunit(region * r, faction * f, int number, const struct race * rc)
{
assert(rc);
return create_unit(r, f, number, rc, 0, NULL, NULL);
}
bool idle(faction * f)
{
return (bool) (f ? false : true);
}
int maxworkingpeasants(const struct region *r)
{
int i = production(r) * MAXPEASANTS_PER_AREA
- ((rtrees(r, 2) + rtrees(r, 1) / 2) * TREESIZE);
return _max(i, 0);
}
int lighthouse_range(const building * b, const faction * f)
{
int d = 0;
if (fval(b, BLD_WORKING) && b->size >= 10) {
int maxd = (int)log10(b->size) + 1;
if (skill_enabled(SK_PERCEPTION)) {
region *r = b->region;
int c = 0;
unit *u;
for (u = r->units; u; u = u->next) {
if (u->building == b) {
c += u->number;
if (c > buildingcapacity(b))
break;
if (f == NULL || u->faction == f) {
int sk = eff_skill(u, SK_PERCEPTION, r) / 3;
d = _max(d, sk);
d = _min(maxd, d);
if (d == maxd)
break;
}
} else if (c)
break; /* first unit that's no longer in the house ends the search */
}
} else {
/* E3A rule: no perception req'd */
return maxd;
}
}
return d;
}
bool check_leuchtturm(region * r, faction * f)
{
attrib *a;
if (!fval(r->terrain, SEA_REGION))
return false;
for (a = a_find(r->attribs, &at_lighthouse); a && a->type == &at_lighthouse;
a = a->next) {
building *b = (building *) a->data.v;
assert(b->type == bt_find("lighthouse"));
if (fval(b, BLD_WORKING) && b->size >= 10) {
int maxd = (int)log10(b->size) + 1;
if (skill_enabled(SK_PERCEPTION)) {
region *r2 = b->region;
unit *u;
int c = 0;
int d = 0;
for (u = r2->units; u; u = u->next) {
if (u->building == b) {
c += u->number;
if (c > buildingcapacity(b))
break;
if (f == NULL || u->faction == f) {
if (!d)
d = distance(r, r2);
if (maxd < d)
break;
if (eff_skill(u, SK_PERCEPTION, r) >= d * 3)
return true;
}
} else if (c)
break; /* first unit that's no longer in the house ends the search */
}
} else {
/* E3A rule: no perception req'd */
return maxd;
}
}
}
return false;
}
region *lastregion(faction * f)
{
#ifdef SMART_INTERVALS
unit *u = f->units;
region *r = f->last;
if (u == NULL)
return NULL;
if (r != NULL)
return r->next;
/* it is safe to start in the region of the first unit. */
f->last = u->region;
/* if regions have indices, we can skip ahead: */
for (u = u->nextF; u != NULL; u = u->nextF) {
r = u->region;
if (r->index > f->last->index)
f->last = r;
}
/* we continue from the best region and look for travelthru etc. */
for (r = f->last->next; r; r = r->next) {
plane *p = rplane(r);
/* search the region for travelthru-attributes: */
if (fval(r, RF_TRAVELUNIT)) {
attrib *ru = a_find(r->attribs, &at_travelunit);
while (ru && ru->type == &at_travelunit) {
u = (unit *) ru->data.v;
if (u->faction == f) {
f->last = r;
break;
}
ru = ru->next;
}
}
if (f->last == r)
continue;
if (check_leuchtturm(r, f))
f->last = r;
if (p && is_watcher(p, f)) {
f->last = r;
}
}
return f->last->next;
#else
return NULL;
#endif
}
region *firstregion(faction * f)
{
#ifdef SMART_INTERVALS
region *r = f->first;
if (f->units == NULL)
return NULL;
if (r != NULL)
return r;
return f->first = regions;
#else
return regions;
#endif
}
void **blk_list[1024];
int list_index;
int blk_index;
static void gc_done(void)
{
int i, k;
for (i = 0; i != list_index; ++i) {
for (k = 0; k != 1024; ++k)
free(blk_list[i][k]);
free(blk_list[i]);
}
for (k = 0; k != blk_index; ++k)
free(blk_list[list_index][k]);
free(blk_list[list_index]);
}
void *gc_add(void *p)
{
if (blk_index == 0) {
blk_list[list_index] = (void **)malloc(1024 * sizeof(void *));
}
blk_list[list_index][blk_index] = p;
blk_index = (blk_index + 1) % 1024;
if (!blk_index)
++list_index;
return p;
}
void add_translation(critbit_tree **cbp, const char *key, int i) {
char buffer[256];
char * str = transliterate(buffer, sizeof(buffer)-sizeof(int), key);
critbit_tree * cb = *cbp;
if (str) {
size_t len = strlen(str);
if (!cb) {
*cbp = cb = (critbit_tree *)calloc(1, sizeof(critbit_tree *));
}
len = cb_new_kv(str, len, &i, sizeof(int), buffer);
cb_insert(cb, buffer, len);
} else {
log_error("could not transliterate '%s'\n", key);
}
}
void init_translations(const struct locale *lang, int ut, const char * (*string_cb)(int i), int maxstrings)
{
void **tokens;
int i;
assert(string_cb);
assert(maxstrings>0);
tokens = get_translations(lang, ut);
for (i = 0; i != maxstrings; ++i) {
const char * s = string_cb(i);
const char * key = s ? locale_string(lang, s) : 0;
key = key ? key : s;
if (key) {
critbit_tree ** cb = (critbit_tree **)tokens;
add_translation(cb, key, i);
}
}
}
static const char * parameter_key(int i)
{
assert(i<MAXPARAMS && i>=0);
return parameters[i];
}
static void init_locale(const struct locale *lang)
{
variant var;
int i;
const struct race *rc;
const terrain_type *terrain;
void **tokens;
tokens = get_translations(lang, UT_MAGIC);
if (tokens) {
const char *str = get_param(global.parameters, "rules.magic.playerschools");
char *sstr, *tok;
if (str == NULL) {
str = "gwyrrd illaun draig cerddor tybied";
}
sstr = _strdup(str);
tok = strtok(sstr, " ");
while (tok) {
for (i = 0; i != MAXMAGIETYP; ++i) {
if (strcmp(tok, magic_school[i]) == 0) break;
}
assert(i != MAXMAGIETYP);
var.i = i;
addtoken(tokens, LOC(lang, mkname("school", tok)), var);
tok = strtok(NULL, " ");
}
free(sstr);
}
init_directions(lang);
init_keywords(lang);
init_skills(lang);
tokens = get_translations(lang, UT_RACES);
for (rc = races; rc; rc = rc->next) {
var.v = (void *)rc;
addtoken(tokens, LOC(lang, rc_name(rc, 1)), var);
addtoken(tokens, LOC(lang, rc_name(rc, 0)), var);
}
init_translations(lang, UT_PARAMS, parameter_key, MAXPARAMS);
tokens = get_translations(lang, UT_OPTIONS);
for (i = 0; i != MAXOPTIONS; ++i) {
var.i = i;
if (options[i]) addtoken(tokens, LOC(lang, options[i]), var);
}
tokens = get_translations(lang, UT_TERRAINS);
for (terrain = terrains(); terrain != NULL; terrain = terrain->next) {
var.v = (void *)terrain;
addtoken(tokens, LOC(lang, terrain->_name), var);
}
}
typedef struct param {
struct param *next;
char *name;
char *data;
} param;
int getid(void)
{
const char *str = (const char *)getstrtoken();
int i = atoi36(str);
if (i < 0) {
return -1;
}
return i;
}
const char *get_param(const struct param *p, const char *key)
{
while (p != NULL) {
int cmp = strcmp(p->name, key);
if (cmp == 0) {
return p->data;
}
else if (cmp>0) {
break;
}
p = p->next;
}
return NULL;
}
int get_param_int(const struct param *p, const char *key, int def)
{
const char * str = get_param(p, key);
return str ? atoi(str) : def;
}
static const char *g_datadir;
const char *datapath(void)
{
static char zText[MAX_PATH];
if (g_datadir)
return g_datadir;
return strcat(strcpy(zText, basepath()), "/data");
}
void set_datapath(const char *path)
{
g_datadir = path;
}
static const char *g_reportdir;
const char *reportpath(void)
{
static char zText[MAX_PATH];
if (g_reportdir)
return g_reportdir;
return strcat(strcpy(zText, basepath()), "/reports");
}
void set_reportpath(const char *path)
{
g_reportdir = path;
}
static const char *g_basedir;
const char *basepath(void)
{
if (g_basedir)
return g_basedir;
return ".";
}
void set_basepath(const char *path)
{
g_basedir = path;
}
float get_param_flt(const struct param *p, const char *key, float def)
{
const char *str = get_param(p, key);
return str ? (float)atof(str) : def;
}
void set_param(struct param **p, const char *key, const char *data)
{
struct param *par;
++global.cookie;
while (*p != NULL) {
int cmp = strcmp((*p)->name, key);
if (cmp == 0) {
free((*p)->data);
(*p)->data = _strdup(data);
return;
}
else if (cmp>0) {
break;
}
p = &(*p)->next;
}
par = malloc(sizeof(param));
par->name = _strdup(key);
par->data = _strdup(data);
par->next = *p;
*p = par;
}
void kernel_done(void)
{
/* calling this function releases memory assigned to static variables, etc.
* calling it is optional, e.g. a release server will most likely not do it.
*/
translation_done();
gc_done();
}
const char *localenames[] = {
"de", "en",
NULL
};
void init_locales(void)
{
int l;
for (l = 0; localenames[l]; ++l) {
const struct locale *lang = get_locale(localenames[l]);
if (!lang) {
lang = get_or_create_locale(localenames[l]);
}
init_locale(lang);
}
}
/* TODO: soll hier weg */
extern struct attrib_type at_shiptrail;
attrib_type at_germs = {
"germs",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
a_writeshorts,
a_readshorts,
ATF_UNIQUE
};
/*********************/
/* at_guard */
/*********************/
attrib_type at_guard = {
"guard",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
a_writeint,
a_readint,
ATF_UNIQUE
};
void setstatus(struct unit *u, int status)
{
assert(status >= ST_AGGRO && status <= ST_FLEE);
if (u->status != status) {
u->status = (status_t) status;
}
}
void setguard(unit * u, unsigned int flags)
{
/* setzt die guard-flags der Einheit */
attrib *a = NULL;
assert(flags == 0 || !fval(u, UFL_MOVED));
assert(flags == 0 || u->status < ST_FLEE);
if (fval(u, UFL_GUARD)) {
a = a_find(u->attribs, &at_guard);
}
if (flags == GUARD_NONE) {
freset(u, UFL_GUARD);
if (a)
a_remove(&u->attribs, a);
return;
}
fset(u, UFL_GUARD);
fset(u->region, RF_GUARDED);
if ((int)flags == guard_flags(u)) {
if (a)
a_remove(&u->attribs, a);
} else {
if (!a)
a = a_add(&u->attribs, a_new(&at_guard));
a->data.i = (int)flags;
}
}
unsigned int getguard(const unit * u)
{
attrib *a;
assert(fval(u, UFL_GUARD) || (u->building && u==building_owner(u->building))
|| !"you're doing it wrong! check is_guard first");
a = a_find(u->attribs, &at_guard);
if (a) {
return (unsigned int)a->data.i;
}
return guard_flags(u);
}
#ifndef HAVE_STRDUP
char *_strdup(const char *s)
{
return strcpy((char *)malloc(sizeof(char) * (strlen(s) + 1)), s);
}
#endif
void remove_empty_factions(void)
{
faction **fp, *f3;
for (fp = &factions; *fp;) {
faction *f = *fp;
/* monster (0) werden nicht entfernt. alive kann beim readgame
* () auf 0 gesetzt werden, wenn monsters keine einheiten mehr
* haben. */
if ((f->units == NULL || f->alive == 0) && !is_monsters(f)) {
ursprung *ur = f->ursprung;
while (ur && ur->id != 0)
ur = ur->next;
if (verbosity >= 2)
log_printf(stdout, "\t%s\n", factionname(f));
/* Einfach in eine Datei schreiben und sp<73>ter vermailen */
if (updatelog)
fprintf(updatelog, "dropout %s\n", itoa36(f->no));
for (f3 = factions; f3; f3 = f3->next) {
ally *sf;
group *g;
ally **sfp = &f3->allies;
while (*sfp) {
sf = *sfp;
if (sf->faction == f || sf->faction == NULL) {
*sfp = sf->next;
free(sf);
} else
sfp = &(*sfp)->next;
}
for (g = f3->groups; g; g = g->next) {
sfp = &g->allies;
while (*sfp) {
sf = *sfp;
if (sf->faction == f || sf->faction == NULL) {
*sfp = sf->next;
free(sf);
} else
sfp = &(*sfp)->next;
}
}
}
*fp = f->next;
funhash(f);
free_faction(f);
free(f);
} else
fp = &(*fp)->next;
}
}
void remove_empty_units_in_region(region * r)
{
unit **up = &r->units;
while (*up) {
unit *u = *up;
if (u->number) {
faction *f = u->faction;
if (f == NULL || !f->alive) {
set_number(u, 0);
}
if (MaxAge() > 0) {
if ((!fval(f, FFL_NOTIMEOUT) && f->age > MaxAge())) {
set_number(u, 0);
}
}
}
if ((u->number == 0 && u_race(u) != get_race(RC_SPELL)) || (u->age <= 0
&& u_race(u) == get_race(RC_SPELL))) {
remove_unit(up, u);
}
if (*up == u)
up = &u->next;
}
}
void remove_empty_units(void)
{
region *r;
for (r = regions; r; r = r->next) {
remove_empty_units_in_region(r);
}
}
bool faction_id_is_unused(int id)
{
return findfaction(id) == NULL;
}
int weight(const unit * u)
{
int w = 0, n = 0, in_bag = 0;
const resource_type *rtype = get_resourcetype(R_SACK_OF_CONSERVATION);
item *itm;
for (itm = u->items; itm; itm = itm->next) {
w = itm->type->weight * itm->number;
n += w;
if (rtype && !fval(itm->type, ITF_BIG)) {
in_bag += w;
}
}
n += u->number * u_race(u)->weight;
if (rtype) {
w = i_get(u->items, rtype->itype) * BAGCAPACITY;
if (w > in_bag) w = in_bag;
n -= w;
}
return n;
}
void make_undead_unit(unit * u)
{
free_orders(&u->orders);
name_unit(u);
fset(u, UFL_ISNEW);
}
unsigned int guard_flags(const unit * u)
{
unsigned int flags =
GUARD_CREWS | GUARD_LANDING | GUARD_TRAVELTHRU | GUARD_TAX;
#if GUARD_DISABLES_PRODUCTION == 1
flags |= GUARD_PRODUCE;
#endif
#if GUARD_DISABLES_RECRUIT == 1
flags |= GUARD_RECRUIT;
#endif
switch (old_race(u_race(u))) {
case RC_ELF:
if (u->faction->race != u_race(u))
break;
/* else fallthrough */
case RC_TREEMAN:
flags |= GUARD_TREES;
break;
case RC_IRONKEEPER:
flags = GUARD_MINING;
break;
default:
/* TODO: This should be configuration variables, all of it */
break;
}
return flags;
}
void guard(unit * u, unsigned int mask)
{
unsigned int flags = guard_flags(u);
setguard(u, flags & mask);
}
int besieged(const unit * u)
{
/* belagert kann man in schiffen und burgen werden */
return (u && !keyword_disabled(K_BESIEGE)
&& u->building && u->building->besieged
&& 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;
for (; itm; itm = itm->next) {
if (itm->type->flags & ITF_ANIMAL)
return true;
}
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<63>den der nicht gestorbenen abziehen. */
u->hp -= hpsub;
/* Meldung nur, wenn noch keine f<>r Tote generiert. */
if (dead == 0) {
/* Durch unzureichende Ern<72>hrung wird %s geschw<68>cht */
ADDMSG(&u->faction->msgs, msg_message("malnourish", "unit region", u, r));
}
}
return (dead || hpsub);
}
void plagues(region * r, bool ismagic)
{
int peasants;
int i;
int dead = 0;
/* Seuchenwahrscheinlichkeit in % */
if (!ismagic) {
double mwp = _max(maxworkingpeasants(r), 1);
double prob =
pow(rpeasants(r) / (mwp * wage(r, NULL, NULL, turn) * 0.13), 4.0)
* PLAGUE_CHANCE;
if (rng_double() >= prob)
return;
}
peasants = rpeasants(r);
dead = (int)(0.5F + PLAGUE_VICTIMS * peasants);
for (i = dead; i != 0; i--) {
if (rng_double() < PLAGUE_HEALCHANCE && rmoney(r) >= PLAGUE_HEALCOST) {
rsetmoney(r, rmoney(r) - PLAGUE_HEALCOST);
--dead;
}
}
if (dead > 0) {
message *msg = add_message(&r->msgs, msg_message("pest", "dead", dead));
msg_release(msg);
deathcounts(r, dead);
rsetpeasants(r, peasants - dead);
}
}
/* Lohn bei den einzelnen Burgstufen f<>r Normale Typen, Orks, Bauern,
* Modifikation f<>r St<53>dter. */
static const int wagetable[7][4] = {
{10, 10, 11, -7}, /* Baustelle */
{10, 10, 11, -5}, /* Handelsposten */
{11, 11, 12, -3}, /* Befestigung */
{12, 11, 13, -1}, /* Turm */
{13, 12, 14, 0}, /* Burg */
{14, 12, 15, 1}, /* Festung */
{15, 13, 16, 2} /* Zitadelle */
};
int cmp_wage(const struct building *b, const building * a)
{
const struct building_type *bt_castle = bt_find("castle");
if (b->type == bt_castle) {
if (!a)
return 1;
if (b->size > a->size)
return 1;
if (b->size == a->size)
return 0;
}
return -1;
}
bool is_owner_building(const struct building * b)
{
region *r = b->region;
if (b->type->taxes && r->land && r->land->ownership) {
unit *u = building_owner(b);
return u && u->faction == r->land->ownership->owner;
}
return false;
}
int cmp_taxes(const building * b, const building * a)
{
faction *f = region_get_owner(b->region);
if (b->type->taxes) {
unit *u = building_owner(b);
if (!u) {
return -1;
} else if (a) {
int newsize = buildingeffsize(b, false);
double newtaxes = b->type->taxes(b, newsize);
int oldsize = buildingeffsize(a, false);
double oldtaxes = a->type->taxes(a, oldsize);
if (newtaxes < oldtaxes)
return -1;
else if (newtaxes > oldtaxes)
return 1;
else if (b->size < a->size)
return -1;
else if (b->size > a->size)
return 1;
else {
if (u && u->faction == f) {
u = building_owner(a);
if (u && u->faction == f)
return -1;
return 1;
}
}
} else {
return 1;
}
}
return -1;
}
int cmp_current_owner(const building * b, const building * a)
{
faction *f = region_get_owner(b->region);
assert(rule_region_owners());
if (f && b->type->taxes) {
unit *u = building_owner(b);
if (!u || u->faction != f)
return -1;
if (a) {
int newsize = buildingeffsize(b, false);
double newtaxes = b->type->taxes(b, newsize);
int oldsize = buildingeffsize(a, false);
double oldtaxes = a->type->taxes(a, oldsize);
if (newtaxes != oldtaxes)
return (newtaxes > oldtaxes) ? 1 : -1;
if (newsize != oldsize)
return newsize - oldsize;
return (b->size - a->size);
} else {
return 1;
}
}
return -1;
}
int rule_stealth_faction(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule = get_param_int(global.parameters, "rules.stealth.faction", 0xFF);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
int rule_region_owners(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule = get_param_int(global.parameters, "rules.region_owners", 0);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
int rule_auto_taxation(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule =
get_param_int(global.parameters, "rules.economy.taxation", TAX_ORDER);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
int rule_blessed_harvest(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule =
get_param_int(global.parameters, "rules.magic.blessed_harvest",
HARVEST_WORK);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
int rule_alliance_limit(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule = get_param_int(global.parameters, "rules.limit.alliance", 0);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
int rule_faction_limit(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule = get_param_int(global.parameters, "rules.limit.faction", 0);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
int rule_transfermen(void)
{
static int gamecookie = -1;
static int rule = -1;
if (rule < 0 || gamecookie != global.cookie) {
rule = get_param_int(global.parameters, "rules.transfermen", 1);
gamecookie = global.cookie;
assert(rule >= 0);
}
return rule;
}
static int
default_wage(const region * r, const faction * f, const race * rc, int in_turn)
{
building *b = largestbuilding(r, &cmp_wage, false);
int esize = 0;
curse *c;
double wage;
attrib *a;
const building_type *artsculpture_type = bt_find("artsculpture");
static const curse_type *drought_ct, *blessedharvest_ct;
static bool init;
if (!init) {
init = true;
drought_ct = ct_find("drought");
blessedharvest_ct = ct_find("blessedharvest");
}
if (b != NULL) {
/* TODO: this reveals imaginary castles */
esize = buildingeffsize(b, false);
}
if (f != NULL) {
int index = 0;
if (rc == get_race(RC_ORC) || rc == get_race(RC_SNOTLING)) {
index = 1;
}
wage = wagetable[esize][index];
} else {
if (is_mourning(r, in_turn)) {
wage = 10;
} else if (fval(r->terrain, SEA_REGION)) {
wage = 11;
} else if (fval(r, RF_ORCIFIED)) {
wage = wagetable[esize][1];
} else {
wage = wagetable[esize][2];
}
if (rule_blessed_harvest() == HARVEST_WORK) {
/* E1 rules */
wage += curse_geteffect(get_curse(r->attribs, blessedharvest_ct));
}
}
/* Artsculpture: Income +5 */
for (b = r->buildings; b; b = b->next) {
if (b->type == artsculpture_type) {
wage += 5;
}
}
/* Godcurse: Income -10 */
if (curse_active(get_curse(r->attribs, ct_find("godcursezone")))) {
wage = _max(0, wage - 10);
}
/* Bei einer D<>rre verdient man nur noch ein Viertel */
if (drought_ct) {
c = get_curse(r->attribs, drought_ct);
if (curse_active(c))
wage /= curse_geteffect(c);
}
a = a_find(r->attribs, &at_reduceproduction);
if (a)
wage = (wage * a->data.sa[0]) / 100;
return (int)wage;
}
static int
minimum_wage(const region * r, const faction * f, const race * rc, int in_turn)
{
if (f && rc) {
return rc->maintenance;
}
return default_wage(r, f, rc, in_turn);
}
/* Gibt Arbeitslohn f<>r entsprechende Rasse zur<75>ck, oder f<>r
* die Bauern wenn f == NULL. */
int wage(const region * r, const faction * f, const race * rc, int in_turn)
{
if (global.functions.wage) {
return global.functions.wage(r, f, rc, in_turn);
}
return default_wage(r, f, rc, in_turn);
}
#define MAINTENANCE 10
int maintenance_cost(const struct unit *u)
{
if (u == NULL)
return MAINTENANCE;
if (global.functions.maintenance) {
int retval = global.functions.maintenance(u);
if (retval >= 0)
return retval;
}
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;
}
int movewhere(const unit * u, const char *token, region * r, region ** resultp)
{
region *r2;
direction_t d;
if (*token == '\0') {
*resultp = NULL;
return E_MOVE_OK;
}
d = get_direction(token, u->faction->locale);
switch (d) {
case D_PAUSE:
*resultp = r;
break;
case NODIRECTION:
r2 = find_special_direction(r, token, u->faction->locale);
if (r2 == NULL) {
return E_MOVE_NOREGION;
}
*resultp = r2;
break;
default:
r2 = rconnect(r, d);
if (r2 == NULL || move_blocked(u, r, r2)) {
return E_MOVE_BLOCKED;
}
*resultp = r2;
}
return E_MOVE_OK;
}
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) {
if (n == 0 || !playerrace(u_race(u)))
return 0;
learn_skill(u, sk, global.producexpchance);
}
return 0;
}
int lovar(double xpct_x2)
{
int n = (int)(xpct_x2 * 500) + 1;
if (n == 0)
return 0;
return (rng_int() % n + rng_int() % n) / 1000;
}
bool has_limited_skills(const struct unit * u)
{
if (has_skill(u, SK_MAGIC) || has_skill(u, SK_ALCHEMY) ||
has_skill(u, SK_TACTICS) || has_skill(u, SK_HERBALISM) ||
has_skill(u, SK_SPY)) {
return true;
} else {
return false;
}
}
void attrib_init(void)
{
/* Alle speicherbaren Attribute m<>ssen hier registriert werden */
at_register(&at_shiptrail);
at_register(&at_familiar);
at_register(&at_familiarmage);
at_register(&at_clone);
at_register(&at_clonemage);
at_register(&at_eventhandler);
at_register(&at_stealth);
at_register(&at_mage);
at_register(&at_countdown);
at_register(&at_curse);
at_register(&at_seenspell);
/* neue REGION-Attribute */
at_register(&at_direction);
at_register(&at_moveblock);
at_register(&at_deathcount);
at_register(&at_chaoscount);
at_register(&at_woodcount);
/* neue UNIT-Attribute */
at_register(&at_siege);
at_register(&at_effect);
at_register(&at_private);
at_register(&at_icastle);
at_register(&at_guard);
at_register(&at_group);
at_register(&at_building_generic_type);
at_register(&at_maxmagicians);
at_register(&at_npcfaction);
/* connection-typen */
register_bordertype(&bt_noway);
register_bordertype(&bt_fogwall);
register_bordertype(&bt_wall);
register_bordertype(&bt_illusionwall);
register_bordertype(&bt_road);
register_bordertype(&bt_questportal);
register_function((pf_generic) & minimum_wage, "minimum_wage");
at_register(&at_germs);
at_deprecate("xontormiaexpress", a_readint); /* required for old datafiles */
at_register(&at_speedup);
at_register(&at_building_action);
}
void kernel_init(void)
{
register_reports();
if (!mt_find("missing_message")) {
mt_register(mt_new_va("missing_message", "name:string", 0));
mt_register(mt_new_va("missing_feedback", "unit:unit", "region:region", "command:order", "name:string", 0));
}
attrib_init();
translation_init();
}
static order * defaults[MAXLOCALES];
order *default_order(const struct locale *lang)
{
static int usedefault = 1;
int i = locale_index(lang);
order *result = 0;
assert(i<MAXLOCALES);
result = defaults[i];
if (!result && usedefault) {
const char * str = locale_string(lang, "defaultorder");
if (str) {
result = defaults[i] = parse_order(str, lang);
} else {
usedefault = 0;
}
}
return result ? copy_order(result) : 0;
}
int entertainmoney(const region * r)
{
double n;
if (is_cursed(r->attribs, C_DEPRESSION, 0)) {
return 0;
}
n = rmoney(r) / ENTERTAINFRACTION;
if (is_cursed(r->attribs, C_GENEROUS, 0)) {
n *= get_curseeffect(r->attribs, C_GENEROUS, 0);
}
return (int)n;
}
int rule_give(void)
{
static int value = -1;
if (value < 0) {
value = get_param_int(global.parameters, "rules.give", GIVE_DEFAULT);
}
return value;
}
int markets_module(void)
{
static int value = -1;
if (value < 0) {
value = get_param_int(global.parameters, "modules.markets", 0);
}
return value;
}
/** releases all memory associated with the game state.
* call this function before calling read_game() to load a new game
* if you have a previously loaded state in memory.
*/
void free_gamedata(void)
{
int i;
free_units();
free_regions();
free_borders();
for (i=0;i!=MAXLOCALES;++i) {
if (defaults[i]) {
free_order(defaults[i]);
defaults[i] = 0;
}
}
while (alliances) {
alliance *al = alliances;
alliances = al->next;
free_alliance(al);
}
while (factions) {
faction *f = factions;
factions = f->next;
funhash(f);
free_faction(f);
free(f);
}
while (planes) {
plane *pl = planes;
planes = planes->next;
free(pl->name);
free(pl);
}
while (global.attribs) {
a_remove(&global.attribs, global.attribs);
}
++global.cookie; /* readgame() already does this, but sjust in case */
init_resources();
}
void load_inifile(dictionary * d)
{
const char *reportdir = reportpath();
const char *datadir = datapath();
const char *basedir = basepath();
const char *str;
assert(d);
str = iniparser_getstring(d, "eressea:base", basedir);
if (str != basedir)
set_basepath(str);
str = iniparser_getstring(d, "eressea:report", reportdir);
if (str != reportdir)
set_reportpath(str);
str = iniparser_getstring(d, "eressea:data", datadir);
if (str != datadir)
set_datapath(str);
lomem = iniparser_getint(d, "eressea:lomem", lomem) ? 1 : 0;
str = iniparser_getstring(d, "eressea:encoding", NULL);
if (str && (_strcmpl(str, "utf8")==0 || _strcmpl(str, "utf-8")==0)) {
enc_gamedata = ENCODING_UTF8;
}
verbosity = iniparser_getint(d, "eressea:verbose", 2);
battledebug = iniparser_getint(d, "eressea:debug", battledebug) ? 1 : 0;
str = iniparser_getstring(d, "eressea:locales", "de,en");
make_locales(str);
/* excerpt from [config] (the rest is used in bindings.c) */
global.game_id = iniparser_getint(d, "config:game_id", 0);
global.inifile = d;
}