/* 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 #include "eressea.h" /* modules includes */ #include /* attributes includes */ #include #include #include #include /* 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 #include #include #include #include #include #include #include /* libxml includes */ #include #include /* libc includes */ #include #include #include #include #include #include #include #include #include #include /* 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;ddirections, 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 (turnattribs, &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)