forked from github/server
3356 lines
67 KiB
C
3356 lines
67 KiB
C
/* vi: set ts=2:
|
|
*
|
|
* Eressea PB(E)M host Copyright (C) 1998-2003
|
|
* Christian Schlittchen (corwin@amber.kn-bremen.de)
|
|
* Katja Zedel (katze@felidae.kn-bremen.de)
|
|
* Henning Peters (faroul@beyond.kn-bremen.de)
|
|
* Enno Rehling (enno@eressea-pbem.de)
|
|
* Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
|
|
*
|
|
* based on:
|
|
*
|
|
* Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace
|
|
* Atlantis v1.7 Copyright 1996 by Alex Schröder
|
|
*
|
|
* This program may not be used, modified or distributed without
|
|
* prior permission by the authors of Eressea.
|
|
* This program may not be sold or used commercially without prior written
|
|
* permission from the authors.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "eressea.h"
|
|
|
|
/* modules includes */
|
|
#include <modules/xecmd.h>
|
|
|
|
/* attributes includes */
|
|
#include <attributes/reduceproduction.h>
|
|
#include <attributes/otherfaction.h>
|
|
#include <attributes/racename.h>
|
|
#include <attributes/gm.h>
|
|
|
|
/* kernel includes */
|
|
#include "alliance.h"
|
|
#include "alchemy.h"
|
|
#include "battle.h"
|
|
#include "border.h"
|
|
#include "building.h"
|
|
#include "calendar.h"
|
|
#include "curse.h"
|
|
#include "faction.h"
|
|
#include "group.h"
|
|
#include "item.h"
|
|
#include "karma.h"
|
|
#include "magic.h"
|
|
#include "message.h"
|
|
#include "movement.h"
|
|
#include "names.h"
|
|
#include "objtypes.h"
|
|
#include "order.h"
|
|
#include "plane.h"
|
|
#include "pool.h"
|
|
#include "race.h"
|
|
#include "region.h"
|
|
#include "save.h"
|
|
#include "ship.h"
|
|
#include "skill.h"
|
|
#include "spell.h"
|
|
#include "unit.h"
|
|
|
|
/* util includes */
|
|
#include <base36.h>
|
|
#include <event.h>
|
|
#include <umlaut.h>
|
|
#include <translation.h>
|
|
#include <crmessage.h>
|
|
#include <log.h>
|
|
#include <sql.h>
|
|
#include <xml.h>
|
|
|
|
/* libxml includes */
|
|
#include <libxml/tree.h>
|
|
#include <libxml/xpath.h>
|
|
|
|
/* libc includes */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <message.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
|
|
/* exported variables */
|
|
region *regions;
|
|
faction *factions;
|
|
settings global;
|
|
char buf[BUFSIZE + 1];
|
|
FILE *logfile;
|
|
FILE *updatelog;
|
|
const struct race * new_race[MAXRACES];
|
|
boolean sqlpatch = false;
|
|
int turn;
|
|
|
|
char *
|
|
strnzcpy(char * dst, const char *src, size_t len)
|
|
{
|
|
strncpy(dst, src, len);
|
|
dst[len]=0;
|
|
return dst;
|
|
}
|
|
|
|
static attrib_type at_creator = {
|
|
"creator"
|
|
/* Rest ist NULL; temporäres, nicht alterndes Attribut */
|
|
};
|
|
|
|
int
|
|
NewbieImmunity(void) {
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "NewbieImmunity");
|
|
value = str?atoi(str):IMMUN_GEGEN_ANGRIFF;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static int
|
|
MaxAge(void) {
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "MaxAge");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
static int
|
|
ally_flag(const char * s)
|
|
{
|
|
if (strcmp(s, "money")==0) return HELP_MONEY;
|
|
if (strcmp(s, "fight")==0) return HELP_FIGHT;
|
|
if (strcmp(s, "observe")==0) return HELP_OBSERVE;
|
|
if (strcmp(s, "give")==0) return HELP_GIVE;
|
|
if (strcmp(s, "guard")==0) return HELP_GUARD;
|
|
if (strcmp(s, "stealth")==0) return HELP_FSTEALTH;
|
|
if (strcmp(s, "travel")==0) return HELP_TRAVEL;
|
|
return 0;
|
|
}
|
|
|
|
boolean
|
|
ExpensiveMigrants(void)
|
|
{
|
|
int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "study.expensivemigrants");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int
|
|
AllianceAuto(void)
|
|
{
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "alliance.auto");
|
|
value = 0;
|
|
if (str!=NULL) {
|
|
char * sstr = strdup(str);
|
|
char * tok = strtok(sstr, " ");
|
|
while (tok) {
|
|
value |= ally_flag(tok);
|
|
tok = strtok(NULL, " ");
|
|
}
|
|
free(sstr);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int
|
|
AllianceRestricted(void)
|
|
{
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "alliance.restricted");
|
|
value = 0;
|
|
if (str!=NULL) {
|
|
char * sstr = strdup(str);
|
|
char * tok = strtok(sstr, " ");
|
|
while (tok) {
|
|
value |= ally_flag(tok);
|
|
tok = strtok(NULL, " ");
|
|
}
|
|
free(sstr);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int
|
|
LongHunger(const struct unit * u) {
|
|
static int value = -1;
|
|
if (u!=NULL) {
|
|
if (!fval(u, UFL_HUNGER)) return false;
|
|
#ifdef NEW_DAEMONHUNGER_RULE
|
|
if (u->race==new_race[RC_DAEMON]) return false;
|
|
#endif
|
|
}
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "hunger.long");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int
|
|
SkillCap(skill_t sk) {
|
|
static int value = -1;
|
|
if (sk==SK_MAGIC) return 0; /* no caps on magic */
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "skill.maxlevel");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
boolean
|
|
TradeDisabled(void) {
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "trade.disabled");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int
|
|
NMRTimeout(void) {
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "nmr.timeout");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
race_t
|
|
old_race(const struct race * rc)
|
|
{
|
|
race_t i;
|
|
for (i=0;i!=MAXRACES;++i) {
|
|
if (new_race[i]==rc) return i;
|
|
}
|
|
return NORACE;
|
|
}
|
|
|
|
const char *directions[MAXDIRECTIONS+2] =
|
|
{
|
|
"northwest",
|
|
"northeast",
|
|
"east",
|
|
"southeast",
|
|
"southwest",
|
|
"west",
|
|
"",
|
|
"pause"
|
|
};
|
|
|
|
const char *
|
|
dbrace(const struct race * rc)
|
|
{
|
|
static char zText[32];
|
|
char * zPtr = zText;
|
|
strcpy(zText, LOC(find_locale("en"), rc_name(rc, 0)));
|
|
while (*zPtr) {
|
|
*zPtr = (char)(toupper(*zPtr));
|
|
++zPtr;
|
|
}
|
|
return zText;
|
|
}
|
|
|
|
const char *parameters[MAXPARAMS] =
|
|
{
|
|
"LOCALE",
|
|
"ALLES",
|
|
"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",
|
|
"UM",
|
|
"BEISTAND",
|
|
"GNADE",
|
|
"HINTER",
|
|
"VOR",
|
|
"ANZAHL",
|
|
"GEGENSTAENDE",
|
|
"TRAENKE",
|
|
"GRUPPE",
|
|
"PARTEITARNUNG",
|
|
"BAEUME",
|
|
"XEPOTION",
|
|
"XEBALLOON",
|
|
"XELAEN"
|
|
};
|
|
|
|
|
|
const char *keywords[MAXKEYWORDS] =
|
|
{
|
|
"//",
|
|
"BANNER",
|
|
"ARBEITEN",
|
|
"ATTACKIEREN",
|
|
"BEKLAUEN",
|
|
"BELAGERE",
|
|
"BENENNEN",
|
|
"BENUTZEN",
|
|
"BESCHREIBEN",
|
|
"BETRETEN",
|
|
"BEWACHEN",
|
|
"BOTSCHAFT",
|
|
"ENDE",
|
|
"FAHREN",
|
|
"NUMMER",
|
|
"KRIEG",
|
|
"FRIEDEN",
|
|
"FOLGEN",
|
|
"FORSCHEN",
|
|
"GIB",
|
|
"HELFEN",
|
|
"KAEMPFEN",
|
|
"KAMPFZAUBER",
|
|
"KAUFEN",
|
|
"KONTAKTIEREN",
|
|
"LEHREN",
|
|
"LERNEN",
|
|
"LIEFERE",
|
|
"MACHEN",
|
|
"NACH",
|
|
"PASSWORT",
|
|
"REKRUTIEREN",
|
|
"RESERVIEREN",
|
|
"ROUTE",
|
|
"SABOTIEREN",
|
|
"OPTION",
|
|
"SPIONIEREN",
|
|
"STIRB",
|
|
"TARNEN",
|
|
"TRANSPORTIEREN",
|
|
"TREIBEN",
|
|
"UNTERHALTEN",
|
|
"VERKAUFEN",
|
|
"VERLASSEN",
|
|
"VERGESSEN",
|
|
"ZAUBERE",
|
|
"ZEIGEN",
|
|
"ZERSTOEREN",
|
|
"ZUECHTEN",
|
|
"DEFAULT",
|
|
"REPORT",
|
|
"URSPRUNG",
|
|
"EMAIL",
|
|
"MEINUNG",
|
|
"MAGIEGEBIET",
|
|
"PIRATERIE",
|
|
"NEUSTART",
|
|
"GRUPPE",
|
|
"OPFERE",
|
|
"BETEN",
|
|
"SORTIEREN",
|
|
"JIHAD",
|
|
"GM",
|
|
"INFO",
|
|
#ifdef USE_UGROUPS
|
|
"JOINVERBAND",
|
|
"LEAVEVERBAND",
|
|
#endif
|
|
"PRAEFIX",
|
|
"SYNONYM",
|
|
"PFLANZEN",
|
|
"WERWESEN",
|
|
"XONTORMIA",
|
|
"ALLIANZ",
|
|
"BEANSPRUCHEN",
|
|
#ifdef HEROES
|
|
"PROMOTION",
|
|
#endif
|
|
};
|
|
|
|
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",
|
|
"SILBERPOOL",
|
|
"STATISTIK",
|
|
"DEBUG",
|
|
"ZIPPED",
|
|
"ZEITUNG", /* Option hat Sonderbehandlung! */
|
|
"MATERIALPOOL",
|
|
"ADRESSEN",
|
|
"BZIP2",
|
|
"PUNKTE",
|
|
"SHOWSKCHANGE"
|
|
};
|
|
|
|
static int
|
|
allied_skillcount(const faction * f, skill_t sk)
|
|
{
|
|
int num = 0;
|
|
alliance * a = f->alliance;
|
|
faction_list * members = a->members;
|
|
while (members!=NULL) {
|
|
num += count_skill(members->data, sk);
|
|
members=members->next;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
static int
|
|
allied_skilllimit(const faction * f, skill_t sk)
|
|
{
|
|
static int value = -1;
|
|
if (value<0) {
|
|
const char * str = get_param(global.parameters, "alliance.skilllimit");
|
|
value = str?atoi(str):0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static void
|
|
init_maxmagicians(struct attrib *a)
|
|
{
|
|
a->data.i = MAXMAGICIANS;
|
|
}
|
|
|
|
static attrib_type at_maxmagicians = {
|
|
"maxmagicians",
|
|
init_maxmagicians,
|
|
NULL,
|
|
NULL,
|
|
a_writedefault,
|
|
a_readdefault,
|
|
ATF_UNIQUE
|
|
};
|
|
|
|
static void
|
|
init_npcfaction(struct attrib *a)
|
|
{
|
|
a->data.i = 1;
|
|
}
|
|
|
|
|
|
static attrib_type at_npcfaction = {
|
|
"npcfaction",
|
|
init_npcfaction,
|
|
NULL,
|
|
NULL,
|
|
a_writedefault,
|
|
a_readdefault,
|
|
ATF_UNIQUE
|
|
};
|
|
|
|
int
|
|
max_skill(faction * f, skill_t sk)
|
|
{
|
|
attrib *a;
|
|
int m = INT_MAX;
|
|
|
|
if (allied_skilllimit(f, sk)) {
|
|
if (sk!=SK_ALCHEMY && sk!=SK_MAGIC) return INT_MAX;
|
|
if (f->alliance!=NULL) {
|
|
int ac = listlen(f->alliance->members); /* number of factions */
|
|
int al = allied_skilllimit(f, sk); /* limit per alliance */
|
|
int fl = (al+ac-1)/ac; /* faction limit */
|
|
int sc = al - allied_skillcount(f, sk);
|
|
if (sc==0) return count_skill(f, sk);
|
|
return fl;
|
|
}
|
|
}
|
|
switch (sk) {
|
|
case SK_MAGIC:
|
|
if((a = a_find(f->attribs, &at_maxmagicians)) != NULL) {
|
|
m = a->data.i;
|
|
} else {
|
|
m = MAXMAGICIANS;
|
|
}
|
|
if (f->race == new_race[RC_ELF]) m += 1;
|
|
m += fspecial(f, FS_MAGOCRACY) * 2;
|
|
break;
|
|
case SK_ALCHEMY:
|
|
m = MAXALCHEMISTS;
|
|
break;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
char * g_basedir;
|
|
char * g_resourcedir;
|
|
|
|
const char *
|
|
basepath(void)
|
|
{
|
|
if (g_basedir) return g_basedir;
|
|
return ".";
|
|
}
|
|
|
|
const char *
|
|
resourcepath(void)
|
|
{
|
|
static char zText[MAX_PATH];
|
|
if (g_resourcedir) return g_resourcedir;
|
|
return strcat(strcpy(zText, basepath()), "/res");
|
|
}
|
|
|
|
int
|
|
count_skill(faction * f, skill_t sk)
|
|
{
|
|
int n = 0;
|
|
unit *u;
|
|
|
|
for (u = f->units; u; u = u->nextF) {
|
|
if (has_skill(u, sk)) {
|
|
if (!is_familiar(u)) n += u->number;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int quiet = 0;
|
|
|
|
FILE *debug;
|
|
|
|
int
|
|
shipspeed (const ship * sh, const unit * u)
|
|
{
|
|
int k = sh->type->range;
|
|
static const curse_type * stormwind_ct, * nodrift_ct;
|
|
static boolean 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 == new_race[RC_AQUARIAN]
|
|
&& u->race == new_race[RC_AQUARIAN]) {
|
|
k += 1;
|
|
}
|
|
|
|
a = a_find(sh->attribs, &at_speedup);
|
|
while (a != NULL) {
|
|
k += a->data.sa[0];
|
|
a = a->nexttype;
|
|
}
|
|
|
|
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 k;
|
|
}
|
|
|
|
/* erwartete Anzahl Einheiten x 2 */
|
|
#define UMAXHASH 199999
|
|
unit *unithash[UMAXHASH];
|
|
|
|
void
|
|
uhash (unit * u)
|
|
{
|
|
assert(!u->nexthash || !"unit ist bereits gehasht");
|
|
u->nexthash = unithash[u->no % UMAXHASH];
|
|
unithash[u->no % UMAXHASH] = u;
|
|
}
|
|
|
|
void
|
|
uunhash (unit * u)
|
|
{
|
|
unit ** x = &(unithash[u->no % UMAXHASH]);
|
|
while (*x && *x!=u) x = &(*x)->nexthash;
|
|
assert(*x || !"unit nicht gefunden");
|
|
*x = u->nexthash;
|
|
u->nexthash=NULL;
|
|
}
|
|
|
|
unit *
|
|
ufindhash (int i)
|
|
{
|
|
unit * u = unithash[i % UMAXHASH];
|
|
while (u && u->no!=i) u = u->nexthash;
|
|
return u;
|
|
}
|
|
|
|
#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
|
|
stripfaction (faction * f)
|
|
{
|
|
/* TODO: inhalt auch löschen */
|
|
if (f->msgs) free_messagelist(f->msgs);
|
|
while (f->battles) {
|
|
struct bmsg * b = f->battles;
|
|
f->battles = b->next;
|
|
if (b->msgs) free_messagelist(b->msgs);
|
|
}
|
|
|
|
freelist(f->allies);
|
|
|
|
free(f->email);
|
|
free(f->banner);
|
|
free(f->passw);
|
|
free(f->override);
|
|
free(f->name);
|
|
|
|
while (f->attribs) a_remove (&f->attribs, f->attribs);
|
|
|
|
i_freeall(f->items);
|
|
|
|
freelist(f->ursprung);
|
|
funhash(f);
|
|
}
|
|
|
|
void
|
|
verify_data(void)
|
|
{
|
|
#ifndef NDEBUG
|
|
int lf = -1;
|
|
faction *f;
|
|
unit *u;
|
|
int mage, alchemist;
|
|
|
|
puts(" - Überprüfe Daten auf Korrektheit...");
|
|
|
|
list_foreach(faction, factions, f) {
|
|
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;
|
|
printf("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 != new_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));
|
|
}
|
|
list_next(f);
|
|
#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 (rand() % old < new_value)
|
|
t++;
|
|
|
|
return t;
|
|
}
|
|
|
|
int
|
|
change_hitpoints (unit * u, int value)
|
|
{
|
|
int hp = u->hp;
|
|
|
|
hp += value;
|
|
|
|
/* Jede Person benötigt mindestens 1 HP */
|
|
if (hp < u->number){
|
|
if (hp < 0){ /* Einheit tot */
|
|
hp = 0;
|
|
}
|
|
scale_number(u, hp);
|
|
}
|
|
u->hp = hp;
|
|
return hp;
|
|
}
|
|
|
|
int
|
|
atoip(const char *s)
|
|
{
|
|
int n;
|
|
|
|
n = atoi (s);
|
|
|
|
if (n < 0)
|
|
n = 0;
|
|
|
|
return n;
|
|
}
|
|
|
|
void
|
|
scat (const char *s)
|
|
{
|
|
strncat (buf, s, BUFSIZE - strlen (buf));
|
|
}
|
|
|
|
void
|
|
icat (int n)
|
|
{
|
|
char s[12];
|
|
|
|
sprintf (s, "%d", n);
|
|
scat (s);
|
|
}
|
|
|
|
|
|
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
|
|
effskill(const unit * u, skill_t sk)
|
|
{
|
|
return eff_skill(u, sk, u->region);
|
|
}
|
|
|
|
int
|
|
effstealth(const unit * u)
|
|
{
|
|
int e, es;
|
|
|
|
/* Auf dem Ozean keine Tarnung! */
|
|
if (u->region->terrain == T_OCEAN) return 0;
|
|
|
|
e = effskill(u, SK_STEALTH);
|
|
|
|
es = u_geteffstealth(u);
|
|
if (es >=0 && es < e) return es;
|
|
return e;
|
|
}
|
|
|
|
int
|
|
eff_stealth (const unit * u, const region * r)
|
|
{
|
|
int e, es;
|
|
|
|
e = eff_skill (u, SK_STEALTH, r);
|
|
|
|
es = u_geteffstealth(u);
|
|
if (es >=0 && es < e) return es;
|
|
return e;
|
|
}
|
|
|
|
void
|
|
scale_number (unit * u, int n)
|
|
{
|
|
skill_t sk;
|
|
const attrib * a;
|
|
int remain;
|
|
|
|
if (n == u->number) return;
|
|
if (n && u->number) {
|
|
int full;
|
|
remain = ((u->hp%u->number) * (n % u->number)) % u->number;
|
|
|
|
full = u->hp/u->number; /* wieviel kriegt jede person mindestens */
|
|
u->hp = full * n + (u->hp-full*u->number) * n / u->number;
|
|
assert(u->hp>=0);
|
|
if ((rand() % u->number) < remain)
|
|
++u->hp; /* Nachkommastellen */
|
|
} else {
|
|
remain = 0;
|
|
u->hp = 0;
|
|
}
|
|
for (a = a_find(u->attribs, &at_effect);a;a=a->nexttype) {
|
|
effect_data * data = (effect_data *)a->data.v;
|
|
int snew = data->value / u->number * n;
|
|
if (n) {
|
|
remain = data->value - snew / n * u->number;
|
|
snew += remain * n / u->number;
|
|
remain = (remain * n) % u->number;
|
|
if ((rand() % u->number) < remain)
|
|
++snew; /* Nachkommastellen */
|
|
}
|
|
data->value = snew;
|
|
}
|
|
if (u->number==0 || n==0) {
|
|
for (sk = 0; sk < MAXSKILLS; sk++) {
|
|
remove_skill(u, sk);
|
|
}
|
|
}
|
|
|
|
set_number(u, n);
|
|
}
|
|
|
|
boolean
|
|
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 boolean 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 (sf->alliance!=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)
|
|
{
|
|
attrib *a;
|
|
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((a = a_find(f->attribs, &at_npcfaction)) != NULL) {
|
|
return mode;
|
|
}
|
|
if((a = a_find(f2->attribs, &at_npcfaction)) != NULL) {
|
|
return mode;
|
|
}
|
|
if (AllianceRestricted() && 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;
|
|
const attrib * a;
|
|
const plane * pl = getplane(u->region);
|
|
int automode;
|
|
|
|
if (u->faction == f2) return mode;
|
|
if (u->faction == NULL || f2==NULL) return 0;
|
|
|
|
automode = mode & autoalliance(pl, u->faction, f2);
|
|
|
|
if (pl!=NULL && (pl->flags & PFL_NOALLIANCES))
|
|
mode = (mode & automode) | (mode & HELP_GIVE);
|
|
|
|
sf = u->faction->allies;
|
|
a = a_findc(u->attribs, &at_group);
|
|
if (a!=NULL) sf = ((group*)a->data.v)->allies;
|
|
return alliedgroup(pl, u->faction, f2, sf, mode);
|
|
}
|
|
|
|
boolean
|
|
seefaction(const faction * f, const region * r, const unit * u, int modifier)
|
|
{
|
|
if (((f == u->faction) || !fval(u, UFL_PARTEITARNUNG)) && cansee(f, r, u, modifier))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
boolean
|
|
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 n;
|
|
boolean cansee = false;
|
|
unit *u2;
|
|
static const item_type * itype_grail;
|
|
static boolean init;
|
|
|
|
if (!init) {
|
|
init = true;
|
|
itype_grail = it_find("grail");
|
|
}
|
|
|
|
if (u->faction == f || omniscient(f)) {
|
|
return true;
|
|
} else if (u->race == new_race[RC_SPELL]) {
|
|
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;
|
|
if (itype_grail!=NULL && i_get(u->items, itype_grail)) return true;
|
|
|
|
n = eff_stealth(u, r) - modifier;
|
|
for (u2 = r->units; u2; u2 = u2->next) {
|
|
if (u2->faction == f) {
|
|
int o;
|
|
|
|
if (getguard(u) || usiege(u) || u->building || u->ship) {
|
|
cansee = true;
|
|
break;
|
|
}
|
|
|
|
#if NEWATSROI == 0
|
|
if (invisible(u) >= u->number
|
|
&& !get_item(u2, I_AMULET_OF_TRUE_SEEING))
|
|
continue;
|
|
#endif
|
|
|
|
o = eff_skill(u2, SK_OBSERVATION, r);
|
|
#ifdef NIGHTEYES
|
|
if (u2->enchanted == SP_NIGHT_EYES && o < NIGHT_EYE_TALENT)
|
|
o = NIGHT_EYE_TALENT;
|
|
#endif
|
|
if (o >= n) {
|
|
cansee = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cansee;
|
|
}
|
|
|
|
|
|
boolean
|
|
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;
|
|
boolean cansee = false;
|
|
unit *u2;
|
|
if (u->race == new_race[RC_SPELL] || u->number == 0) return false;
|
|
else if (u->faction == f) cansee = true;
|
|
else {
|
|
|
|
if (getguard(u) || usiege(u) || u->building || u->ship) {
|
|
cansee = true;
|
|
}
|
|
|
|
n = eff_stealth(u, r) - modifier;
|
|
if (n<=0) {
|
|
cansee = true;
|
|
}
|
|
|
|
for (u2 = r->units; !cansee && u2; u2 = u2->next){
|
|
if (u2->faction == f) {
|
|
int o;
|
|
|
|
#if NEWATSROI == 0
|
|
if (invisible(u) >= u->number
|
|
&& !get_item(u2, I_AMULET_OF_TRUE_SEEING))
|
|
continue;
|
|
#endif
|
|
|
|
o = eff_skill(u2, SK_OBSERVATION, r);
|
|
|
|
#ifdef NIGHTEYES
|
|
if (u2->enchanted == SP_NIGHT_EYES && o < NIGHT_EYE_TALENT)
|
|
o = NIGHT_EYE_TALENT;
|
|
#endif
|
|
if (o >= n) {
|
|
cansee = true;
|
|
}
|
|
}
|
|
}
|
|
if (getguard(u) || usiege(u) || u->building || u->ship) {
|
|
cansee = true;
|
|
}
|
|
}
|
|
return cansee;
|
|
}
|
|
|
|
#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ürzt:\n%s\n",
|
|
(int)maxlen, s));
|
|
strnzcpy(buffer, s, maxlen);
|
|
return buffer;
|
|
}
|
|
return s;
|
|
}
|
|
#endif
|
|
|
|
static attrib_type at_lighthouse = {
|
|
"lighthouse"
|
|
/* Rest ist NULL; temporä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)
|
|
{
|
|
region * r = lh->region;
|
|
short d = (short)log10(lh->size) + 1;
|
|
short x, y;
|
|
static const struct building_type * bt_lighthouse;
|
|
if (!bt_lighthouse) bt_lighthouse = bt_find("lighthouse");
|
|
assert(bt_lighthouse);
|
|
|
|
if (lh->type!=bt_lighthouse) return;
|
|
|
|
for (x=-d;x<=d;++x) {
|
|
for (y=-d;y<=d;++y) {
|
|
attrib * a;
|
|
region * r2 = findregion(x+r->x, y+r->y);
|
|
if (rterrain(r2)!=T_OCEAN) continue;
|
|
if (!r2 || distance(r, r2) > d) continue;
|
|
a = a_find(r2->attribs, &at_lighthouse);
|
|
while (a) {
|
|
building * b = (building*)a->data.v;
|
|
if (b==lh) break;
|
|
a=a->nexttype;
|
|
}
|
|
if (!a) {
|
|
a = a_add(&r2->attribs, a_new(&at_lighthouse));
|
|
a->data.v = (void*)lh;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
count_all(const faction * f)
|
|
{
|
|
#ifndef NDEBUG
|
|
int n = 0;
|
|
unit *u;
|
|
for (u=f->units;u;u=u->nextF) {
|
|
if (playerrace(u->race)) {
|
|
n += u->number;
|
|
assert(f==u->faction);
|
|
}
|
|
}
|
|
if (f->num_people != n) {
|
|
log_error(("Anzahl Personen für (%s) ist != num_people: %d statt %d.\n",
|
|
factionid(f), f->num_migrants, n));
|
|
return n;
|
|
}
|
|
#endif
|
|
return f->num_people;
|
|
}
|
|
|
|
int
|
|
count_migrants (const faction * f)
|
|
{
|
|
#ifndef NDEBUG
|
|
unit *u = f->units;
|
|
int n = 0;
|
|
while (u) {
|
|
assert(u->faction == f);
|
|
if (u->race != f->race && u->race != new_race[RC_ILLUSION] && u->race != new_race[RC_SPELL]
|
|
&& !!playerrace(u->race) && !(is_cursed(u->attribs, C_SLAVE, 0)))
|
|
{
|
|
n += u->number;
|
|
}
|
|
u = u->nextF;
|
|
}
|
|
if (f->num_migrants != n) {
|
|
log_error(("Anzahl Migranten für (%s) ist != num_migrants: %d statt %d.\n",
|
|
factionid(f), f->num_migrants, n));
|
|
return n;
|
|
}
|
|
#endif
|
|
return f->num_migrants;
|
|
}
|
|
|
|
int
|
|
count_maxmigrants(const faction * f)
|
|
{
|
|
int x = 0;
|
|
if (f->race == new_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;
|
|
}
|
|
|
|
/*------------------------------------------------------------------*/
|
|
|
|
/* GET STR, I zur Eingabe von Daten liest diese aus dem Buffer, der beim ersten
|
|
* Aufruf inititialisiert wird? */
|
|
|
|
typedef struct parser_state {
|
|
const unsigned char *current_token;
|
|
char * current_cmd;
|
|
struct parser_state * next;
|
|
} parser_state;
|
|
|
|
static parser_state * state;
|
|
|
|
void
|
|
init_tokens_str(const char * initstr)
|
|
{
|
|
if (state==NULL) {
|
|
state = malloc(sizeof(parser_state));
|
|
state->current_cmd = NULL;
|
|
}
|
|
state->current_token = (const unsigned char *)initstr;
|
|
}
|
|
|
|
void
|
|
init_tokens(const struct order * ord)
|
|
{
|
|
if (state==NULL) state = malloc(sizeof(parser_state));
|
|
else if (state->current_cmd!=NULL) free(state->current_cmd);
|
|
state->current_cmd = getcommand(ord);
|
|
init_tokens_str(state->current_cmd);
|
|
}
|
|
|
|
void
|
|
parser_pushstate(void)
|
|
{
|
|
parser_state * new_state = malloc(sizeof(parser_state));
|
|
new_state->current_cmd = NULL;
|
|
new_state->current_token = NULL;
|
|
new_state->next = state;
|
|
state = new_state;
|
|
}
|
|
|
|
void
|
|
parser_popstate(void)
|
|
{
|
|
parser_state * new_state = state->next;
|
|
if (state->current_cmd!=NULL) free(state->current_cmd);
|
|
free(state);
|
|
state = new_state;
|
|
}
|
|
|
|
boolean
|
|
parser_end(void)
|
|
{
|
|
while (isspace(*state->current_token)) ++state->current_token;
|
|
return *state->current_token == 0;
|
|
}
|
|
|
|
void
|
|
skip_token(void)
|
|
{
|
|
char quotechar = 0;
|
|
|
|
while (isspace(*state->current_token)) ++state->current_token;
|
|
while (*state->current_token) {
|
|
if (isspace(*state->current_token) && quotechar==0) {
|
|
return;
|
|
} else {
|
|
switch(*state->current_token) {
|
|
case '"':
|
|
case '\'':
|
|
if (*state->current_token==quotechar) return;
|
|
quotechar = *state->current_token;
|
|
break;
|
|
case ESCAPE_CHAR:
|
|
++state->current_token;
|
|
break;
|
|
}
|
|
}
|
|
++state->current_token;
|
|
}
|
|
}
|
|
|
|
void
|
|
parse(keyword_t kword, int (*dofun)(unit *, struct order *), boolean 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 (get_keyword(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 *
|
|
parse_token(const char ** str)
|
|
{
|
|
static char lbuf[DISPLAYSIZE + 1];
|
|
char * cursor = lbuf;
|
|
char quotechar = 0;
|
|
boolean escape = false;
|
|
const unsigned char * ctoken = (const unsigned char*)*str;
|
|
|
|
assert(ctoken);
|
|
|
|
while (isspace(*ctoken)) ++ctoken;
|
|
while (*ctoken && cursor-lbuf < DISPLAYSIZE) {
|
|
if (escape) {
|
|
*cursor++ = *ctoken++;
|
|
} else if (isspace(*ctoken)) {
|
|
if (quotechar==0) break;
|
|
*cursor++ = *ctoken++;
|
|
} else if (*ctoken=='"' || *ctoken=='\'') {
|
|
if (*ctoken==quotechar) {
|
|
++ctoken;
|
|
break;
|
|
} else if (quotechar==0) {
|
|
quotechar = *ctoken;
|
|
++ctoken;
|
|
} else {
|
|
*cursor++ = *ctoken++;
|
|
}
|
|
} else if (*ctoken==SPACE_REPLACEMENT) {
|
|
*cursor++ = ' ';
|
|
++ctoken;
|
|
} else if (*ctoken==ESCAPE_CHAR) {
|
|
escape = true;
|
|
++ctoken;
|
|
} else {
|
|
*cursor++ = *ctoken++;
|
|
}
|
|
}
|
|
|
|
*cursor = '\0';
|
|
*str = (const char *)ctoken;
|
|
return lbuf;
|
|
}
|
|
|
|
const char *
|
|
igetstrtoken(const char * initstr)
|
|
{
|
|
if (initstr!=NULL) {
|
|
init_tokens_str(initstr);
|
|
}
|
|
|
|
return parse_token((const char**)&state->current_token);
|
|
}
|
|
|
|
const char *
|
|
getstrtoken(void)
|
|
{
|
|
return parse_token((const char**)&state->current_token);
|
|
}
|
|
|
|
int
|
|
geti (void)
|
|
{
|
|
return atoip (getstrtoken ());
|
|
}
|
|
|
|
/* GET KEYWORD, SKILL, ITEM, SPELL benutzen FINDSTR - welche Item um Item eine
|
|
* Liste durchsucht.
|
|
*
|
|
* FIND wird immer von GET aufgerufen. GET braucht keine Parameter, IGET braucht
|
|
* einen String aus dem gelesen wird. In FIND stehen dann listen etc drinnen.
|
|
* FIND kann man auch allein verwenden, wenn der string _nur_ noch das gesuchte
|
|
* object enthaelt. Steht noch weitere info darin, sollte man GET verwenden,
|
|
* bzw. GETI wenn die info am Anfang eines neuen Stringes steht. */
|
|
|
|
int
|
|
findstr(const char **v, const char *s, unsigned char n)
|
|
{
|
|
int i;
|
|
size_t ss = strlen(s);
|
|
if (!ss)
|
|
return -1;
|
|
for (i = 0; i != n; i++)
|
|
if (!strncasecmp(s, v[i], ss))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
enum {
|
|
UT_NONE,
|
|
UT_PARAM,
|
|
UT_ITEM,
|
|
UT_BUILDING,
|
|
UT_HERB,
|
|
UT_POTION,
|
|
UT_MAX
|
|
};
|
|
|
|
static struct lstr {
|
|
const struct locale * lang;
|
|
struct tnode tokens[UT_MAX];
|
|
struct tnode skillnames;
|
|
struct tnode keywords;
|
|
struct tnode races;
|
|
struct tnode directions;
|
|
struct tnode options;
|
|
struct lstr * next;
|
|
} * lstrs;
|
|
|
|
static struct lstr *
|
|
get_lnames(const struct locale * lang)
|
|
{
|
|
static struct lstr * lnames = NULL;
|
|
static const struct locale * lastlang = NULL;
|
|
|
|
if (lastlang!=lang || lnames==NULL) {
|
|
lnames = lstrs;
|
|
while (lnames && lnames->lang!=lang) lnames = lnames->next;
|
|
if (lnames==NULL) {
|
|
lnames = calloc(sizeof(struct lstr), 1);
|
|
lnames->lang = lang;
|
|
lnames->next = lstrs;
|
|
lstrs = lnames;
|
|
}
|
|
}
|
|
return lnames;
|
|
}
|
|
|
|
const struct race *
|
|
findrace(const char * s, const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
variant token;
|
|
|
|
if (findtoken(&lnames->races, s, &token)==E_TOK_SUCCESS) {
|
|
return (const struct race *)token.v;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
findoption(const char *s, const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
variant token;
|
|
|
|
if (findtoken(&lnames->options, s, &token)==E_TOK_SUCCESS) {
|
|
return (direction_t)token.i;
|
|
}
|
|
return NODIRECTION;
|
|
}
|
|
|
|
skill_t
|
|
findskill(const char *s, const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
variant token;
|
|
|
|
if (findtoken(&lnames->skillnames, s, &token)==E_TOK_NOMATCH) return NOSKILL;
|
|
return (skill_t)token.i;
|
|
}
|
|
|
|
keyword_t
|
|
findkeyword(const char *s, const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
variant token;
|
|
#ifdef AT_PERSISTENT
|
|
if (*s == '@') s++;
|
|
#endif
|
|
if (findtoken(&lnames->keywords, s, &token)==E_TOK_NOMATCH) return NOKEYWORD;
|
|
if (global.disabled[token.i]) return NOKEYWORD;
|
|
return (keyword_t) token.i;
|
|
}
|
|
|
|
param_t
|
|
findparam(const char *s, const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
const building_type * btype;
|
|
variant token;
|
|
|
|
if (findtoken(&lnames->tokens[UT_PARAM], s, &token)==E_TOK_NOMATCH) {
|
|
btype = findbuildingtype(s, lang);
|
|
if (btype!=NULL) return (param_t) P_GEBAEUDE;
|
|
return NOPARAM;
|
|
}
|
|
if (token.i==P_BUILDING) return P_GEBAEUDE;
|
|
return (param_t)token.i;
|
|
}
|
|
|
|
param_t
|
|
getparam (const struct locale * lang)
|
|
{
|
|
return findparam (getstrtoken (), lang);
|
|
}
|
|
|
|
#ifdef FUZZY_BASE36
|
|
extern int fuzzy_hits;
|
|
boolean enable_fuzzy = false;
|
|
#endif /* FUZZY_BASE36 */
|
|
|
|
faction *
|
|
findfaction (int n)
|
|
{
|
|
faction * f = ffindhash(n);
|
|
return f;
|
|
#ifdef FUZZY_BASE36
|
|
if(enable_fuzzy) {
|
|
n = atoi(itoa36(n));
|
|
if (n) {
|
|
f = ffindhash(n);
|
|
if (f) return f;
|
|
for (f = factions; f; f = f->next) {
|
|
if (f->no == n) {
|
|
fhash(f);
|
|
return (f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
#endif /* FUZZY_BASE36 */
|
|
}
|
|
|
|
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)
|
|
{
|
|
return findunitg(n, NULL);
|
|
}
|
|
|
|
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! */
|
|
|
|
switch (findparam (s, f->locale)) {
|
|
case P_TEMP:
|
|
return read_newunitid(f, r);
|
|
}
|
|
if (!s || *s == 0)
|
|
return -1;
|
|
return atoi36(s);
|
|
}
|
|
|
|
/* exported symbol */
|
|
boolean 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;
|
|
}
|
|
}
|
|
|
|
/* - Meldungen und Fehler ------------------------------------------------- */
|
|
|
|
boolean lomem = false;
|
|
|
|
/* - Namen der Strukturen -------------------------------------- */
|
|
typedef char name[OBJECTIDSIZE + 1];
|
|
static name idbuf[8];
|
|
static int nextbuf = 0;
|
|
|
|
char *
|
|
estring(const char *s)
|
|
{
|
|
char *buf = idbuf[(++nextbuf) % 8];
|
|
char *r;
|
|
|
|
strcpy(buf,s);
|
|
r = buf;
|
|
|
|
while(*buf) {
|
|
if(*buf == ' ') {
|
|
*buf = '~';
|
|
}
|
|
buf++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
char *
|
|
cstring(const char *s)
|
|
{
|
|
char *buf = idbuf[(++nextbuf) % 8];
|
|
char *r;
|
|
|
|
strcpy(buf,s);
|
|
r = buf;
|
|
|
|
while(*buf) {
|
|
if(*buf == '~') {
|
|
*buf = ' ';
|
|
}
|
|
buf++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
const char *
|
|
buildingname (const building * b)
|
|
{
|
|
char *buf = idbuf[(++nextbuf) % 8];
|
|
|
|
sprintf(buf, "%s (%s)", strcheck(b->name, NAMESIZE), itoa36(b->no));
|
|
return buf;
|
|
}
|
|
|
|
building *
|
|
largestbuilding (const region * r, boolean img)
|
|
{
|
|
static const building_type * btype = NULL;
|
|
building *b, *best = NULL;
|
|
if (!btype) btype = bt_find("castle"); /* TODO: parameter der funktion? */
|
|
/* durch die verw. von '>' statt '>=' werden die aelteren burgen
|
|
* bevorzugt. */
|
|
|
|
for (b = rbuildings(r); b; b = b->next) {
|
|
if (b->type!=btype) {
|
|
if (img) {
|
|
const attrib * a = a_find(b->attribs, &at_icastle);
|
|
if (!a) continue;
|
|
if (a->data.v != btype) continue;
|
|
} else continue;
|
|
}
|
|
if (best==NULL || b->size > best->size)
|
|
best = b;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
const char *
|
|
unitname(const unit * u)
|
|
{
|
|
char *ubuf = idbuf[(++nextbuf) % 8];
|
|
sprintf(ubuf, "%s (%s)", strcheck(u->name, NAMESIZE), itoa36(u->no));
|
|
return ubuf;
|
|
}
|
|
|
|
/* -- 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 + (rand() % 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 + (rand() % 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;
|
|
}
|
|
|
|
static void
|
|
createunitid(unit *u, int id)
|
|
{
|
|
if (id<=0 || id > MAX_UNIT_NR || ufindhash(id) || dfindhash(id) || forbiddenid(id))
|
|
u->no = newunitid();
|
|
else
|
|
u->no = id;
|
|
uhash(u);
|
|
}
|
|
|
|
unit *
|
|
createunit(region * r, faction * f, int number, const struct race * rc)
|
|
{
|
|
assert(rc);
|
|
return create_unit(r, f, number, rc, 0, NULL, NULL);
|
|
}
|
|
|
|
unit *
|
|
create_unit(region * r, faction * f, int number, const struct race *urace, int id, const char * dname, unit *creator)
|
|
{
|
|
unit * u = calloc(1, sizeof(unit));
|
|
order * deford = default_order(f->locale);
|
|
|
|
assert(urace);
|
|
assert(f->alive);
|
|
u_setfaction(u, f);
|
|
set_order(&u->thisorder, NULL);
|
|
#ifdef LASTORDER
|
|
set_order(&u->lastorder, deford);
|
|
#else
|
|
addlist(&u->orders, deford);
|
|
#endif
|
|
u_seteffstealth(u, -1);
|
|
u->race = urace;
|
|
u->irace = urace;
|
|
|
|
set_number(u, number);
|
|
|
|
/* die nummer der neuen einheit muss vor name_unit generiert werden,
|
|
* da der default name immer noch 'Nummer u->no' ist */
|
|
createunitid(u, id);
|
|
|
|
/* zuerst in die Region setzen, da zb Drachennamen den Regionsnamen
|
|
* enthalten */
|
|
move_unit(u, r, NULL);
|
|
|
|
/* u->race muss bereits gesetzt sein, wird für default-hp gebraucht */
|
|
/* u->region auch */
|
|
u->hp = unit_max_hp(u) * number;
|
|
|
|
if (!dname) {
|
|
name_unit(u);
|
|
}
|
|
else set_string(&u->name, dname);
|
|
set_string(&u->display, "");
|
|
|
|
/* Nicht zu der Einheitenzahl zählen sollten auch alle Monster. Da
|
|
* aber auf die MAXUNITS nur in MACHE TEMP geprüft wird, ist es egal */
|
|
if (playerrace(u->race)) {
|
|
f->no_units++;
|
|
}
|
|
|
|
if (creator) {
|
|
attrib * a;
|
|
|
|
/* erbt Kampfstatus */
|
|
u->status = creator->status;
|
|
|
|
/* erbt Gebäude/Schiff*/
|
|
if (creator->region==r) {
|
|
u->building = creator->building;
|
|
u->ship = creator->ship;
|
|
}
|
|
|
|
/* Temps von parteigetarnten Einheiten sind wieder parteigetarnt */
|
|
if (fval(creator, UFL_PARTEITARNUNG))
|
|
fset(u, UFL_PARTEITARNUNG);
|
|
|
|
/* Daemonentarnung */
|
|
set_racename(&u->attribs, get_racename(creator->attribs));
|
|
if (fval(u->race, RCF_SHAPESHIFT) && fval(creator->race, RCF_SHAPESHIFT)) {
|
|
u->irace = creator->irace;
|
|
}
|
|
|
|
/* Gruppen */
|
|
a = a_find(creator->attribs, &at_group);
|
|
if (a) {
|
|
group * g = (group*)a->data.v;
|
|
a_add(&u->attribs, a_new(&at_group))->data.v = g;
|
|
}
|
|
a = a_find(creator->attribs, &at_otherfaction);
|
|
if (a) {
|
|
a_add(&u->attribs, make_otherfaction(get_otherfaction(a)));
|
|
}
|
|
|
|
a = a_add(&u->attribs, a_new(&at_creator));
|
|
a->data.v = creator;
|
|
}
|
|
/* Monster sind grundsätzlich parteigetarnt */
|
|
if(f->no <= 0) fset(u, UFL_PARTEITARNUNG);
|
|
|
|
return u;
|
|
}
|
|
|
|
/* Setzt Default Befehle -------------------------------------- */
|
|
|
|
boolean
|
|
idle (faction * f)
|
|
{
|
|
return (boolean) (f ? false : true);
|
|
}
|
|
|
|
|
|
int
|
|
maxworkingpeasants(const struct region * r)
|
|
{
|
|
#if GROWING_TREES
|
|
int i = production(r) * MAXPEASANTS_PER_AREA
|
|
- ((rtrees(r,2)+rtrees(r,1)/2) * TREESIZE);
|
|
#else
|
|
int i = production(r) * MAXPEASANTS_PER_AREA - rtrees(r) * TREESIZE;
|
|
#endif
|
|
return max(i, 0);
|
|
}
|
|
|
|
unit_list *
|
|
get_lighthouses(const region * r)
|
|
{
|
|
attrib * a;
|
|
unit_list * ulist = NULL;
|
|
|
|
if (rterrain(r) != T_OCEAN) return NULL;
|
|
|
|
for (a = a_find(r->attribs, &at_lighthouse);a;a=a->nexttype) {
|
|
building *b = (building *)a->data.v;
|
|
region *r2 = b->region;
|
|
|
|
if (fval(b, BLD_WORKING) && b->size >= 10) {
|
|
boolean c = false;
|
|
unit *u;
|
|
int d = distance(r, r2);
|
|
int maxd = (int)log10(b->size) + 1;
|
|
|
|
if (maxd < d) break;
|
|
|
|
for (u = r2->units; u; u = u->next) {
|
|
if (u->building == b) {
|
|
c = true;
|
|
if (c > buildingcapacity(b)) break;
|
|
if (eff_skill(u, SK_OBSERVATION, r) >= d * 3) {
|
|
unitlist_insert(&ulist, u);
|
|
}
|
|
} else if (c) break; /* first unit that's no longer in the house ends the search */
|
|
}
|
|
}
|
|
}
|
|
return ulist;
|
|
}
|
|
|
|
int
|
|
lighthouse_range(const building * b, const faction * f)
|
|
{
|
|
int d = 0;
|
|
if (fval(b, BLD_WORKING) && b->size >= 10) {
|
|
region * r = b->region;
|
|
unit *u;
|
|
int maxd = (int)log10(b->size) + 1;
|
|
int c = 0;
|
|
|
|
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_OBSERVATION, 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 */
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
|
|
boolean
|
|
check_leuchtturm(region * r, faction * f)
|
|
{
|
|
attrib * a;
|
|
|
|
if (rterrain(r) != T_OCEAN) return false;
|
|
|
|
for (a = a_find(r->attribs, &at_lighthouse);a;a=a->nexttype) {
|
|
building *b = (building *)a->data.v;
|
|
region *r2 = b->region;
|
|
|
|
assert(b->type == bt_find("lighthouse"));
|
|
if (fval(b, BLD_WORKING) && b->size >= 10) {
|
|
int c = 0;
|
|
unit *u;
|
|
int d = 0;
|
|
int maxd = (int)log10(b->size) + 1;
|
|
|
|
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_OBSERVATION, r) >= d * 3) return true;
|
|
}
|
|
} else if (c) break; /* first unit that's no longer in the house ends the search */
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
#ifdef ENUM_REGIONS
|
|
/* 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;
|
|
}
|
|
#endif
|
|
/* we continue from the best region and look for travelthru etc. */
|
|
for (r = f->last->next; r; r = r->next) {
|
|
plane * p = rplane(r);
|
|
attrib *ru;
|
|
|
|
#ifndef ENUM_REGIONS
|
|
/* for index-regions we don't need this, we already did it earlier */
|
|
for (u = r->units; u; u = u->next) {
|
|
if (u->faction == f) {
|
|
f->last = r;
|
|
break;
|
|
}
|
|
}
|
|
if (f->last == r) continue;
|
|
#endif
|
|
/* search the region for travelthru-attributes: */
|
|
for (ru = a_find(r->attribs, &at_travelunit); ru; ru = ru->nexttype) {
|
|
u = (unit*)ru->data.v;
|
|
if (u->faction == f) {
|
|
f->last = r;
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
|
|
#ifndef ENUM_REGIONS
|
|
for (r = regions; r; r = r->next) {
|
|
attrib *ru;
|
|
unit *u;
|
|
plane * p = rplane(r);
|
|
for (u = r->units; u; u = u->next) {
|
|
if (u->faction == f) {
|
|
return f->first = r;
|
|
}
|
|
}
|
|
if (f->first == r->next)
|
|
continue;
|
|
for (ru = a_find(r->attribs, &at_travelunit); ru; ru = ru->nexttype) {
|
|
u = (unit*)ru->data.v;
|
|
if (u->faction == f) {
|
|
return f->first = r;
|
|
}
|
|
}
|
|
if (check_leuchtturm(r, f)) {
|
|
return f->first = r;
|
|
}
|
|
if (p && is_watcher(p, f)) {
|
|
return f->first = r;
|
|
}
|
|
}
|
|
#endif
|
|
return f->first = regions;
|
|
#else
|
|
return regions;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
update_intervals(void)
|
|
{
|
|
#ifdef SMART_INTERVALS
|
|
region *r;
|
|
for (r = regions; r; r = r->next) {
|
|
plane * p = rplane(r);
|
|
|
|
if (p!=NULL) {
|
|
struct watcher * w = p->watchers;
|
|
while (w) {
|
|
if (w->faction!=NULL) {
|
|
update_interval(w->faction, r);
|
|
}
|
|
w = w->next;
|
|
}
|
|
}
|
|
}
|
|
#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
|
|
use_birthdayamulet(region * r, unit * magician, order * ord)
|
|
{
|
|
region *tr;
|
|
direction_t d;
|
|
|
|
unused(ord);
|
|
unused(magician);
|
|
|
|
for(d=0;d<MAXDIRECTIONS;d++) {
|
|
tr = rconnect(r, d);
|
|
if(tr) addmessage(tr, 0, "Miiauuuuuu...", MSG_MESSAGE, ML_IMPORTANT);
|
|
}
|
|
|
|
tr = r;
|
|
addmessage(r, 0, "Miiauuuuuu...", MSG_MESSAGE, ML_IMPORTANT);
|
|
}
|
|
|
|
static void
|
|
init_directions(tnode * root, const struct locale * lang)
|
|
{
|
|
/* mit dieser routine kann man mehrere namen für eine direction geben,
|
|
* das ist für die hexes ideal. */
|
|
const struct {
|
|
const char* name;
|
|
int direction;
|
|
} dirs [] = {
|
|
{ "dir_ne", D_NORTHEAST},
|
|
{ "dir_nw", D_NORTHWEST},
|
|
{ "dir_se", D_SOUTHEAST},
|
|
{ "dir_sw", D_SOUTHWEST},
|
|
{ "dir_east", D_EAST},
|
|
{ "dir_west", D_WEST},
|
|
{ "northeast", D_NORTHEAST},
|
|
{ "northwest", D_NORTHWEST},
|
|
{ "southeast", D_SOUTHEAST},
|
|
{ "southwest", D_SOUTHWEST},
|
|
{ "east", D_EAST },
|
|
{ "west",D_WEST },
|
|
{ "PAUSE", D_PAUSE },
|
|
{ NULL, NODIRECTION}
|
|
};
|
|
int i;
|
|
struct lstr * lnames = get_lnames(lang);
|
|
for (i=0; dirs[i].direction!=NODIRECTION;++i) {
|
|
variant token;
|
|
token.i = dirs[i].direction;
|
|
addtoken(&lnames->directions, LOC(lang, dirs[i].name), token);
|
|
}
|
|
}
|
|
|
|
direction_t
|
|
finddirection(const char *s, const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
variant token;
|
|
|
|
if (findtoken(&lnames->directions, s, &token)==E_TOK_SUCCESS) {
|
|
return (direction_t)token.i;
|
|
}
|
|
return NODIRECTION;
|
|
}
|
|
|
|
static void
|
|
init_locale(const struct locale * lang)
|
|
{
|
|
struct lstr * lnames = get_lnames(lang);
|
|
variant var;
|
|
int i;
|
|
const struct race * rc;
|
|
|
|
init_directions(&lnames->directions, lang);
|
|
for (rc=races;rc;rc=rc->next) {
|
|
var.v = (void*)rc;
|
|
addtoken(&lnames->races, LOC(lang, rc_name(rc, 1)), var);
|
|
addtoken(&lnames->races, LOC(lang, rc_name(rc, 0)), var);
|
|
}
|
|
for (i=0;i!=MAXPARAMS;++i) {
|
|
var.i = i;
|
|
addtoken(&lnames->tokens[UT_PARAM], LOC(lang, parameters[i]), var);
|
|
}
|
|
for (i=0;i!=MAXSKILLS;++i) {
|
|
if (i!=SK_TRADE || !TradeDisabled()) {
|
|
var.i = i;
|
|
addtoken(&lnames->skillnames, skillname((skill_t)i, lang), var);
|
|
}
|
|
}
|
|
for (i=0;i!=MAXKEYWORDS;++i) {
|
|
var.i = i;
|
|
addtoken(&lnames->keywords, LOC(lang, keywords[i]), var);
|
|
}
|
|
for (i=0;i!=MAXOPTIONS;++i) {
|
|
var.i = i;
|
|
addtoken(&lnames->options, LOC(lang, options[i]), var);
|
|
}
|
|
}
|
|
|
|
typedef struct param {
|
|
struct param * next;
|
|
char * name;
|
|
char * data;
|
|
} param;
|
|
|
|
const char *
|
|
get_param(const struct param * p, const char * key)
|
|
{
|
|
while (p!=NULL) {
|
|
if (strcmp(p->name, key)==0) return p->data;
|
|
p = p->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
set_param(struct param ** p, const char * key, const char * data)
|
|
{
|
|
while (*p!=NULL) {
|
|
if (strcmp((*p)->name, key)==0) {
|
|
free((*p)->data);
|
|
(*p)->data = strdup(data);
|
|
return;
|
|
}
|
|
p=&(*p)->next;
|
|
}
|
|
*p = malloc(sizeof(param));
|
|
(*p)->name = strdup(key);
|
|
(*p)->data = strdup(data);
|
|
(*p)->next = NULL;
|
|
}
|
|
|
|
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();
|
|
skill_done();
|
|
gc_done();
|
|
sql_done();
|
|
}
|
|
|
|
const char * localenames[] = {
|
|
"de", "en",
|
|
NULL
|
|
};
|
|
|
|
int
|
|
init_data(const char * filename)
|
|
{
|
|
int l;
|
|
char zText[80];
|
|
|
|
sprintf(zText, "%s/%s", resourcepath(), filename);
|
|
l = read_xml(zText);
|
|
if (l) return l;
|
|
|
|
if (turn<first_turn) turn = first_turn;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
init_locales(void)
|
|
{
|
|
int l;
|
|
for (l=0;localenames[l];++l) {
|
|
const struct locale * lang = find_locale(localenames[l]);
|
|
if (lang) init_locale(lang);
|
|
}
|
|
}
|
|
|
|
/* TODO: soll hier weg */
|
|
extern attrib_type at_shiptrail;
|
|
|
|
attrib_type at_germs = {
|
|
"germs",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
DEFAULT_WRITE,
|
|
DEFAULT_READ,
|
|
ATF_UNIQUE
|
|
};
|
|
|
|
/*********************/
|
|
/* at_guard */
|
|
/*********************/
|
|
static attrib_type at_guard = {
|
|
"guard",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
DEFAULT_WRITE,
|
|
DEFAULT_READ,
|
|
ATF_UNIQUE
|
|
};
|
|
|
|
void
|
|
setguard(unit * u, unsigned int flags)
|
|
{
|
|
/* setzt die guard-flags der Einheit */
|
|
attrib * a = a_find(u->attribs, &at_guard);
|
|
if(flags == GUARD_NONE) {
|
|
if(a) a_remove(&u->attribs, a);
|
|
return;
|
|
}
|
|
if (!a) a = a_add(&u->attribs, a_new(&at_guard));
|
|
a->data.i = (int)flags;
|
|
}
|
|
|
|
unsigned int
|
|
getguard(const unit * u)
|
|
{
|
|
attrib *a;
|
|
|
|
if(u->region->terrain == T_OCEAN) return GUARD_NONE;
|
|
a = a_find(u->attribs, &at_guard);
|
|
if (a) return (unsigned int)a->data.i;
|
|
return GUARD_NONE;
|
|
}
|
|
|
|
#ifndef HAVE_STRDUP
|
|
char *
|
|
strdup(const char *s)
|
|
{
|
|
return strcpy((char*)malloc(sizeof(char)*(strlen(s)+1)), s);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
remove_empty_factions(boolean writedropouts)
|
|
{
|
|
faction **fp, *f3;
|
|
FILE *dofp = NULL;
|
|
char zText[MAX_PATH];
|
|
sprintf(zText, "%s/dropouts.%d", basepath(), turn);
|
|
|
|
if (writedropouts) dofp = fopen(zText, "w");
|
|
|
|
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) && f->no != MONSTER_FACTION) {
|
|
ursprung * ur = f->ursprung;
|
|
while (ur && ur->id!=0) ur=ur->next;
|
|
if (!quiet) printf("\t%s\n", factionname(f));
|
|
|
|
/* Einfach in eine Datei schreiben und später vermailen */
|
|
|
|
if (dofp) fprintf(dofp, "%s %s %d %d %d\n", f->email, LOC(default_locale, rc_name(f->race, 0)), f->age, ur?ur->x:0, ur?ur->y:0);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
if (f->subscription) {
|
|
sql_print(("UPDATE subscriptions set status='DEAD' where id=%u\n;",
|
|
f->subscription));
|
|
}
|
|
|
|
*fp = f->next;
|
|
}
|
|
else fp = &(*fp)->next;
|
|
}
|
|
|
|
if (dofp) fclose(dofp);
|
|
}
|
|
|
|
void
|
|
remove_empty_units_in_region(region *r)
|
|
{
|
|
unit **up = &r->units;
|
|
|
|
while (*up) {
|
|
unit * u = *up;
|
|
if (MaxAge()>0) {
|
|
faction * f = u->faction;
|
|
if (!fval(f, FFL_NOTIMEOUT) && f->age > MaxAge()) set_number(u, 0);
|
|
}
|
|
if ((u->number == 0 && u->race != new_race[RC_SPELL])
|
|
|| (u->age <= 0 && u->race == new_race[RC_SPELL])) {
|
|
destroy_unit(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);
|
|
}
|
|
}
|
|
|
|
int *used_faction_ids = NULL;
|
|
int no_used_faction_ids = 0;
|
|
|
|
static int
|
|
_cmp_int(const void *i1, const void *i2)
|
|
{
|
|
if(i2==NULL)
|
|
return(*(int*)i1);
|
|
return (*(int*)i1 - *(int *)i2);
|
|
}
|
|
|
|
void
|
|
register_faction_id(int id)
|
|
{
|
|
no_used_faction_ids++;
|
|
used_faction_ids = realloc(used_faction_ids, no_used_faction_ids*sizeof(int));
|
|
used_faction_ids[no_used_faction_ids-1] = id;
|
|
if(no_used_faction_ids > 1)
|
|
qsort(used_faction_ids, (size_t)no_used_faction_ids, sizeof(int), _cmp_int);
|
|
}
|
|
|
|
boolean
|
|
faction_id_is_unused(int id)
|
|
{
|
|
#if 0
|
|
if(used_faction_ids==NULL)
|
|
return(true);
|
|
return (boolean)(bsearch(&id, used_faction_ids, no_used_faction_ids,
|
|
sizeof(int), _cmp_int) == NULL);
|
|
#else
|
|
return findfaction(id)==NULL;
|
|
#endif
|
|
}
|
|
|
|
int
|
|
weight(const unit * u)
|
|
{
|
|
int w, n = 0, in_bag = 0;
|
|
int faerie_level;
|
|
|
|
item * itm;
|
|
for (itm=u->items;itm;itm=itm->next) {
|
|
w = itm->type->weight * itm->number;
|
|
n += w;
|
|
if( !fval(itm->type, ITF_BIG))
|
|
in_bag += w;
|
|
}
|
|
|
|
faerie_level = fspecial(u->faction, FS_FAERIE);
|
|
if (faerie_level) {
|
|
n += (u->number * u->race->weight)/(1+faerie_level);
|
|
} else {
|
|
n += u->number * u->race->weight;
|
|
}
|
|
|
|
w = get_item(u, I_BAG_OF_HOLDING) * BAGCAPACITY;
|
|
if( w > in_bag )
|
|
w = in_bag;
|
|
n -= w;
|
|
|
|
return n;
|
|
}
|
|
|
|
void
|
|
init_used_faction_ids(void)
|
|
{
|
|
faction *f;
|
|
|
|
no_used_faction_ids = 0;
|
|
for(f = factions; f; f = f->next) {
|
|
register_faction_id(f->no);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
make_undead_unit(unit * u)
|
|
{
|
|
#ifdef LASTORDER
|
|
set_order(&u->lastorder, NULL);
|
|
#endif
|
|
free_orders(&u->orders);
|
|
name_unit(u);
|
|
fset(u, UFL_ISNEW);
|
|
}
|
|
|
|
void
|
|
guard(unit * u, unsigned int mask)
|
|
{
|
|
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)) {
|
|
case RC_ELF:
|
|
if (u->faction->race != u->race) break;
|
|
/* else fallthrough */
|
|
case RC_TREEMAN:
|
|
flags |= GUARD_TREES;
|
|
break;
|
|
case RC_IRONKEEPER:
|
|
flags = GUARD_MINING;
|
|
break;
|
|
}
|
|
setguard(u, flags & mask);
|
|
}
|
|
|
|
int
|
|
besieged(const unit * u)
|
|
{
|
|
/* belagert kann man in schiffen und burgen werden */
|
|
return (u
|
|
&& u->building && u->building->besieged
|
|
&& u->building->besieged >= u->building->size * SIEGEFACTOR);
|
|
}
|
|
|
|
int
|
|
lifestyle(const unit * u)
|
|
{
|
|
static plane * astralspace = NULL;
|
|
int need;
|
|
|
|
if (u->faction->no == MONSTER_FACTION) return 0;
|
|
|
|
need = u->number * u->race->maintenance;
|
|
|
|
if (!astralspace) {
|
|
astralspace = getplanebyname("Astralraum");
|
|
}
|
|
|
|
#ifndef ASTRAL_HUNGER
|
|
/* Keinen Unterhalt im Astralraum. */
|
|
if (getplane(u->region) == astralspace)
|
|
return 0;
|
|
#endif
|
|
if(u->region->planep && fval(u->region->planep, PFL_NOFEED))
|
|
return 0;
|
|
|
|
if(fspecial(u->faction, FS_REGENERATION))
|
|
need += 1;
|
|
if(fspecial(u->faction, FS_ADMINISTRATOR))
|
|
need += 1;
|
|
if(fspecial(u->faction, FS_WYRM) && u->race == new_race[RC_WYRM])
|
|
need *= 500;
|
|
|
|
return need;
|
|
}
|
|
|
|
boolean
|
|
hunger(int number, unit * u)
|
|
{
|
|
region * r = u->region;
|
|
int dead = 0, hpsub = 0;
|
|
int hp = u->hp / u->number;
|
|
|
|
while (number--) {
|
|
int dam = u->race==new_race[RC_HALFLING]?15+rand()%14:(13+rand()%12);
|
|
if (dam >= hp) {
|
|
++dead;
|
|
} else {
|
|
hpsub += dam;
|
|
}
|
|
}
|
|
|
|
if (dead) {
|
|
/* Gestorbene aus der Einheit nehmen,
|
|
* Sie bekommen keine Beerdingung. */
|
|
ADDMSG(&u->faction->msgs, new_message(u->faction,
|
|
"starvation%u:unit%r:region%i:dead%i:live", u, r, dead, u->number-dead));
|
|
|
|
scale_number(u, u->number - dead);
|
|
deathcounts(r, dead);
|
|
}
|
|
if (hpsub > 0) {
|
|
/* Jetzt die Schäden der nicht gestorbenen abziehen. */
|
|
u->hp -= hpsub;
|
|
/* Meldung nur, wenn noch keine für Tote generiert. */
|
|
if (dead == 0) {
|
|
/* Durch unzureichende Ernährung wird %s geschwächt */
|
|
add_message(&u->faction->msgs, new_message(u->faction,
|
|
"malnourish%u:unit%r:region", u, r));
|
|
}
|
|
}
|
|
return (dead || hpsub);
|
|
}
|
|
|
|
void
|
|
plagues(region * r, boolean ismagic)
|
|
{
|
|
double prob;
|
|
int peasants;
|
|
int i;
|
|
int gestorben;
|
|
/* Vermeidung von DivByZero */
|
|
double mwp = max(maxworkingpeasants(r), 1);
|
|
|
|
/* Seuchenwahrscheinlichkeit in % */
|
|
|
|
prob = pow((double) rpeasants(r) /
|
|
(mwp * (((double)wage(r,NULL,false)) / 10.0) * 1.3), 4.0)
|
|
* (double) SEUCHE;
|
|
|
|
if (rand() % 100 >= (int)prob && !ismagic) return;
|
|
|
|
peasants = rpeasants(r);
|
|
for (i = peasants; i != 0; i--) {
|
|
if (rand() % 100 < SEUCHENOPFER) {
|
|
if (rand() % 100 < HEILCHANCE && rmoney(r) >= HEILKOSTEN) {
|
|
rsetmoney(r, rmoney(r) - HEILKOSTEN);
|
|
} else {
|
|
peasants--;
|
|
}
|
|
}
|
|
}
|
|
|
|
gestorben = rpeasants(r) - peasants;
|
|
|
|
if (gestorben > 0) {
|
|
message * msg = add_message(&r->msgs, msg_message("pest", "dead", gestorben));
|
|
msg_release(msg);
|
|
deathcounts(r, gestorben);
|
|
}
|
|
rsetpeasants(r, peasants);
|
|
}
|
|
|
|
/* Lohn bei den einzelnen Burgstufen für Normale Typen, Orks, Bauern,
|
|
* Modifikation für Städter. */
|
|
|
|
#if LARGE_CASTLES
|
|
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 */
|
|
};
|
|
#else
|
|
static const int wagetable[7][4] = {
|
|
{10, 10, 11, -5}, /* Baustelle */
|
|
{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 */
|
|
};
|
|
#endif
|
|
|
|
int
|
|
wage(const region *r, const unit *u, boolean img)
|
|
/* Gibt Arbeitslohn für entsprechende Rasse zurück, oder für
|
|
* die Bauern wenn ra == NORACE. */
|
|
{
|
|
building *b = largestbuilding(r, img);
|
|
int esize = 0;
|
|
curse * c;
|
|
int wage;
|
|
attrib *a;
|
|
const building_type *artsculpture_type = bt_find("artsculpture");
|
|
static const curse_type * drought_ct, * blessedharvest_ct;
|
|
static boolean init;
|
|
|
|
if (!init) {
|
|
init = true;
|
|
drought_ct = ct_find("drought");
|
|
blessedharvest_ct = ct_find("blessedharvest");
|
|
}
|
|
|
|
if (b) esize = buildingeffsize(b, img);
|
|
|
|
if (u) {
|
|
wage = wagetable[esize][u->race == new_race[RC_ORC] || u->race == new_race[RC_SNOTLING] || u->race == new_race[RC_URUK]];
|
|
if (fspecial(u->faction, FS_URBAN)) {
|
|
wage += wagetable[esize][3];
|
|
}
|
|
} else {
|
|
if (rterrain(r) == T_OCEAN) {
|
|
wage = 11;
|
|
} else if (fval(r, RF_ORCIFIED)) {
|
|
wage = wagetable[esize][1];
|
|
} else {
|
|
wage = wagetable[esize][2];
|
|
}
|
|
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 wage;
|
|
}
|
|
|
|
int
|
|
fwage(const region *r, const faction *f, boolean img)
|
|
{
|
|
building *b = largestbuilding(r, img);
|
|
int esize = 0;
|
|
int wage;
|
|
attrib *a;
|
|
curse * c;
|
|
|
|
if (b) esize = buildingeffsize(b, img);
|
|
|
|
if (f) {
|
|
wage = wagetable[esize][f->race == new_race[RC_ORC]];
|
|
if (fspecial(f, FS_URBAN)) {
|
|
wage += wagetable[esize][3];
|
|
}
|
|
} else {
|
|
if (rterrain(r) == T_OCEAN) {
|
|
wage = 11;
|
|
} else if (fval(r, RF_ORCIFIED)) {
|
|
wage = wagetable[esize][1];
|
|
} else {
|
|
wage = wagetable[esize][2];
|
|
}
|
|
wage += curse_geteffect(get_curse(r->attribs, ct_find("blessedharvest")));
|
|
}
|
|
|
|
/* 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 */
|
|
c = get_curse(r->attribs, ct_find("drought"));
|
|
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 wage;
|
|
}
|
|
|
|
static region *
|
|
findspecialdirection(const region *r, const char *token)
|
|
{
|
|
attrib *a;
|
|
spec_direction *d;
|
|
|
|
if (strlen(token)==0) return NULL;
|
|
for (a = a_find(r->attribs, &at_direction);a;a=a->nexttype) {
|
|
d = (spec_direction *)(a->data.v);
|
|
|
|
if (d->active && strncasecmp(d->keyword, token, strlen(token)) == 0) {
|
|
return findregion(d->x, d->y);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
message *
|
|
movement_error(unit * u, const char * token, order * ord, int error_code)
|
|
{
|
|
direction_t d;
|
|
switch (error_code) {
|
|
case E_MOVE_BLOCKED:
|
|
d = finddirection(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 = finddirection(token, u->faction->locale);
|
|
switch (d) {
|
|
case D_PAUSE:
|
|
*resultp = r;
|
|
break;
|
|
|
|
case NODIRECTION:
|
|
r2 = findspecialdirection(r, token);
|
|
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;
|
|
}
|
|
|
|
boolean
|
|
move_blocked(const unit * u, const region *r, const region *r2)
|
|
{
|
|
border * 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;
|
|
add_message(&u->faction->msgs, new_message(u->faction, "income%u:unit%r:region%i:mode%i:wanted%i:amount",
|
|
u, u->region, type, want, qty));
|
|
}
|
|
|
|
void
|
|
reorder_owners(region * r)
|
|
{
|
|
unit ** up=&r->units, ** useek;
|
|
building * b=NULL;
|
|
ship * sh=NULL;
|
|
#ifndef NDEBUG
|
|
size_t len = listlen(r->units);
|
|
#endif
|
|
for (b = r->buildings;b;b=b->next) {
|
|
unit ** ubegin = up;
|
|
unit ** uend = up;
|
|
|
|
useek = up;
|
|
while (*useek) {
|
|
unit * u = *useek;
|
|
if (u->building==b) {
|
|
unit ** insert;
|
|
if (fval(u, UFL_OWNER)) {
|
|
unit * nu = *ubegin;
|
|
insert=ubegin;
|
|
if (nu!=u && nu->building==u->building && fval(nu, UFL_OWNER)) {
|
|
log_error(("[reorder_owners] %s hat mehrere Besitzer mit UFL_OWNER.\n", buildingname(nu->building)));
|
|
freset(nu, UFL_OWNER);
|
|
}
|
|
}
|
|
else insert = uend;
|
|
if (insert!=useek) {
|
|
*useek = u->next; /* raus aus der liste */
|
|
u->next = *insert;
|
|
*insert = u;
|
|
}
|
|
if (insert==uend) uend=&u->next;
|
|
}
|
|
if (*useek==u) useek = &u->next;
|
|
}
|
|
up = uend;
|
|
}
|
|
|
|
useek=up;
|
|
while (*useek) {
|
|
unit * u = *useek;
|
|
assert(!u->building);
|
|
if (u->ship==NULL) {
|
|
if (fval(u, UFL_OWNER)) {
|
|
log_warning(("[reorder_owners] Einheit %s war Besitzer von nichts.\n", unitname(u)));
|
|
freset(u, UFL_OWNER);
|
|
}
|
|
if (useek!=up) {
|
|
*useek = u->next; /* raus aus der liste */
|
|
u->next = *up;
|
|
*up = u;
|
|
}
|
|
up = &u->next;
|
|
}
|
|
if (*useek==u) useek = &u->next;
|
|
}
|
|
|
|
for (sh = r->ships;sh;sh=sh->next) {
|
|
unit ** ubegin = up;
|
|
unit ** uend = up;
|
|
|
|
useek = up;
|
|
while (*useek) {
|
|
unit * u = *useek;
|
|
if (u->ship==sh) {
|
|
unit ** insert;
|
|
if (fval(u, UFL_OWNER)) {
|
|
unit * nu = *ubegin;
|
|
insert = ubegin;
|
|
if (nu!=u && nu->ship==u->ship && fval(nu, UFL_OWNER)) {
|
|
log_error(("[reorder_owners] %s hat mehrere Besitzer mit UFL_OWNER.\n", shipname(nu->ship)));
|
|
freset(nu, UFL_OWNER);
|
|
}
|
|
}
|
|
else insert = uend;
|
|
if (insert!=useek) {
|
|
*useek = u->next; /* raus aus der liste */
|
|
u->next = *insert;
|
|
*insert = u;
|
|
}
|
|
if (insert==uend) uend=&u->next;
|
|
}
|
|
if (*useek==u) useek = &u->next;
|
|
}
|
|
up = uend;
|
|
}
|
|
#ifndef NDEBUG
|
|
assert(len==listlen(r->units));
|
|
#endif
|
|
}
|
|
|
|
int
|
|
produceexp(struct unit * u, skill_t sk, int n)
|
|
{
|
|
if (n==0 || !playerrace(u->race)) return 0;
|
|
learn_skill(u, sk, PRODUCEEXP/30.0);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lovar(double xpct_x2)
|
|
{
|
|
int n = (int)(xpct_x2 * 500)+1;
|
|
if (n==0) return 0;
|
|
return (rand() % n + rand() % n)/1000;
|
|
}
|
|
|
|
boolean
|
|
teure_talente (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_unitdissolve);
|
|
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_cursewall);
|
|
|
|
at_register(&at_seenspell);
|
|
at_register(&at_deathcloud);
|
|
|
|
/* neue REGION-Attribute */
|
|
at_register(&at_direction);
|
|
at_register(&at_moveblock);
|
|
at_register(&at_deathcount);
|
|
at_register(&at_chaoscount);
|
|
at_register(&at_woodcount);
|
|
at_register(&at_road);
|
|
|
|
/* 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_faction_special);
|
|
at_register(&at_prayer_timeout);
|
|
at_register(&at_wyrm);
|
|
at_register(&at_building_generic_type);
|
|
at_register(&at_maxmagicians);
|
|
at_register(&at_npcfaction);
|
|
|
|
/* border-typen */
|
|
register_bordertype(&bt_noway);
|
|
register_bordertype(&bt_fogwall);
|
|
register_bordertype(&bt_wall);
|
|
register_bordertype(&bt_illusionwall);
|
|
register_bordertype(&bt_firewall);
|
|
register_bordertype(&bt_wisps);
|
|
register_bordertype(&bt_chaosgate);
|
|
register_bordertype(&bt_road);
|
|
register_bordertype(&bt_questportal);
|
|
|
|
at_register(&at_jihad);
|
|
#if GROWING_TREES
|
|
at_register(&at_germs);
|
|
#endif
|
|
at_register(&at_laen); /* required for old datafiles */
|
|
#ifdef XECMD_MODULE
|
|
at_register(&at_xontormiaexpress); /* required for old datafiles */
|
|
#endif
|
|
#ifdef WDW_PYRAMIDSPELL
|
|
at_register(&at_wdwpyramid);
|
|
#endif
|
|
at_register(&at_speedup);
|
|
at_register(&at_nodestroy);
|
|
at_register(&at_building_action);
|
|
}
|
|
|
|
void
|
|
kernel_init(void)
|
|
{
|
|
char zBuffer[MAX_PATH];
|
|
skill_init();
|
|
attrib_init();
|
|
translation_init();
|
|
|
|
if (!turn) turn = lastturn();
|
|
if (turn == 0)
|
|
srand((int)time(0));
|
|
else
|
|
srand(turn);
|
|
if (sqlpatch) {
|
|
sprintf(zBuffer, "%s/patch-%d.sql", datapath(), turn);
|
|
sql_init(zBuffer);
|
|
}
|
|
}
|
|
|
|
order *
|
|
default_order(const struct locale * lang)
|
|
{
|
|
return parse_order(locale_string(lang, "defaultorder"), lang);
|
|
}
|
|
|
|
int
|
|
entertainmoney(const region *r)
|
|
{
|
|
int 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 n;
|
|
}
|
|
|
|
int
|
|
freadstr(FILE * F, char * start, size_t size)
|
|
{
|
|
char * str = start;
|
|
boolean quote = false;
|
|
int nread = 0;
|
|
for (;;) {
|
|
int c = fgetc(F);
|
|
|
|
++nread;
|
|
if (isspace(c)) {
|
|
if (str==start) {
|
|
continue;
|
|
}
|
|
if (!quote) {
|
|
*str = 0;
|
|
return nread;
|
|
}
|
|
}
|
|
switch (c) {
|
|
case EOF:
|
|
return EOF;
|
|
case '"':
|
|
if (!quote && str!=start) {
|
|
log_error(("datafile contains a \" that isn't at the start of a string.\n"));
|
|
assert(!"datafile contains a \" that isn't at the start of a string.\n");
|
|
}
|
|
if (quote) {
|
|
*str = 0;
|
|
return nread;
|
|
}
|
|
quote = true;
|
|
break;
|
|
case '\\':
|
|
c = fgetc(F);
|
|
++nread;
|
|
switch (c) {
|
|
case EOF:
|
|
return EOF;
|
|
case 'n':
|
|
if ((size_t)(str-start+1)<size) {
|
|
*str++ = '\n';
|
|
}
|
|
break;
|
|
default:
|
|
if ((size_t)(str-start+1)<size) {
|
|
*str++ = (char)c;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
*str++ = (char)c;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** writes a quoted string to the file
|
|
* no trailing space, since this is used to make the creport.
|
|
*/
|
|
int
|
|
fwritestr(FILE * F, const char * str)
|
|
{
|
|
int nwrite = 0;
|
|
fputc('\"', F);
|
|
while (*str) {
|
|
int c = (int)(unsigned char)*str++;
|
|
switch (c) {
|
|
case '"':
|
|
case '\\':
|
|
fputc('\\', F);
|
|
fputc(c, F);
|
|
nwrite+=2;
|
|
break;
|
|
case '\n':
|
|
fputc('\\', F);
|
|
fputc('n', F);
|
|
nwrite+=2;
|
|
break;
|
|
default:
|
|
fputc(c, F);
|
|
++nwrite;
|
|
}
|
|
}
|
|
fputc('\"', F);
|
|
return nwrite + 2;
|
|
}
|