#ifdef _MSC_VER #include #endif #include #include "laws.h" #include #include "alchemy.h" #include "automate.h" #include "battle.h" #include "contact.h" #include "economy.h" #include "give.h" #include "market.h" #include "morale.h" #include "monsters.h" #include "move.h" #include "randenc.h" #include "recruit.h" #include "renumber.h" #include "spy.h" #include "study.h" #include "wormhole.h" #include "prefix.h" #include "reports.h" #include "teleport.h" #include "guard.h" #include "volcano.h" /* kernel includes */ #include "kernel/alliance.h" #include "kernel/ally.h" #include "kernel/calendar.h" #include "kernel/callbacks.h" #include "kernel/connection.h" #include "kernel/curse.h" #include "kernel/building.h" #include "kernel/faction.h" #include "kernel/group.h" #include "kernel/item.h" #include "kernel/messages.h" #include "kernel/order.h" #include "kernel/plane.h" #include "kernel/pool.h" #include "kernel/race.h" #include "kernel/region.h" #include "kernel/resources.h" #include "kernel/ship.h" #include "kernel/spell.h" #include "kernel/spellbook.h" #include "kernel/terrain.h" #include "kernel/terrainid.h" #include "kernel/unit.h" /* util includes */ #include #include #include #include #include "util/keyword.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* attributes includes */ #include #include #include #include #include #include #include #include #include #include /* libc includes */ #include #include #include #include #include #include #include #include /* chance that a peasant dies of starvation: */ #define PEASANT_STARVATION_CHANCE 0.9 /* Pferdevermehrung */ #define HORSEGROWTH 4 /* Wanderungschance pro Pferd */ #define HORSEMOVE 3 /* Vermehrungschance pro Baum */ #define FORESTGROWTH 10000 /* In Millionstel */ /** Ausbreitung und Vermehrung */ #define MAXDEMAND 25 #define DMRISE 0.1F /* weekly chance that demand goes up */ #define DMRISEHAFEN 0.2F /* weekly chance that demand goes up with harbor */ param_t findparam_ex(const char *s, const struct locale * lang) { param_t result = findparam(s, lang); if (result == NOPARAM) { const building_type *btype = findbuildingtype(s, lang); if (btype != NULL) return P_GEBAEUDE; } return (result == P_BUILDING) ? P_GEBAEUDE : result; } int NewbieImmunity(void) { int result = config_get_int("NewbieImmunity", 0); return result; } bool IsImmune(const faction * f) { return !fval(f, FFL_NPC) && f->age < NewbieImmunity(); } int NMRTimeout(void) { static int config, nmr_timeout, ini_timeout; if (config_changed(&config)) { nmr_timeout = config_get_int("nmr.timeout", 0); ini_timeout = config_get_int("game.maxnmr", 0); } if (nmr_timeout > 0) { if (ini_timeout > nmr_timeout) { return nmr_timeout; } return (ini_timeout > 0) ? ini_timeout : nmr_timeout; } return ini_timeout; } bool LongHunger(const struct unit *u) { if (u != NULL) { if (!fval(u, UFL_HUNGER)) return false; if (u_race(u) == get_race(RC_DAEMON)) return false; } return config_get_int("hunger.long", 0) != 0; } static bool RemoveNMRNewbie(void) { int value = config_get_int("nmr.removenewbie", 0); return value != 0; } static void dumbeffect(unit *u) { int effect = get_effect(u, oldpotiontype[P_FOOL]); if (effect > 0) { /* Trank "Dumpfbackenbrot" */ skill *sv = u->skills, *sb = NULL; while (sv != u->skills + u->skill_size) { if (sb == NULL || skill_compare(sv, sb) > 0) { sb = sv; } ++sv; } /* bestes Talent raussuchen */ if (sb != NULL) { int weeks = u->number; if (weeks > effect) weeks = effect; reduce_skill(u, sb, weeks); ADDMSG(&u->faction->msgs, msg_message("dumbeffect", "unit weeks skill", u, weeks, (skill_t)sb->id)); } /* sonst Glueck gehabt: wer nix weiss, kann nix vergessen... */ change_effect(u, oldpotiontype[P_FOOL], -effect); } } static void astral_crumble(unit *u) { item **itemp = &u->items; while (*itemp) { item *itm = *itemp; if ((itm->type->flags & ITF_NOTLOST) == 0) { if (itm->type->flags & (ITF_BIG | ITF_ANIMAL | ITF_CURSED)) { ADDMSG(&u->faction->msgs, msg_message("itemcrumble", "unit region item amount", u, u->region, itm->type->rtype, itm->number)); i_free(i_remove(itemp, itm)); continue; } } itemp = &itm->next; } } static void age_unit(region * r, unit * u) { const race *rc = u_race(u); ++u->age; if (u->number > 0 && rc->age_unit) { rc->age_unit(u); } if (u->attribs) { dumbeffect(u); } if (u->items && u->region && is_astral(u->region)) { astral_crumble(u); } } static void live(region * r) { unit **up = &r->units; get_food(r); while (*up) { unit *u = *up; /* IUW: age_unit() kann u loeschen, u->next ist dann * undefiniert, also muessen wir hier schon das naechste * Element bestimmen */ age_unit(r, u); if (*up == u) up = &u->next; } } /* * This procedure calculates the number of emigrating peasants for the given * region r. There are two incentives for peasants to emigrate: * 1) They prefer the less crowded areas. * Example: mountains, 700 peasants (max 1000), 70% inhabited * plain, 5000 peasants (max 10000), 50% inhabited * 700*(PEASANTSWANDER_WEIGHT/100)*((70-50)/100) peasants wander * from mountains to plain. * Effect : peasents will leave densely populated regions. * 2) Peasants prefer richer neighbour regions. * Example: region A, 700 peasants, wealth $10500, $15 per head * region B, 2500 peasants, wealth $25000, $10 per head * Some peasants will emigrate from B to A because $15 > $10 * exactly: 2500*(PEASANTSGREED_WEIGHT1/100)*((15-10)/100) * Not taken in consideration: * - movement because of monsters. * - movement because of wars * - movement because of low loyalty relating to present parties. */ #define MAX_EMIGRATION(p) ((p)/MAXDIRECTIONS) #define MAX_IMMIGRATION(p) ((p)*2/3) void peasant_migration(region * r) { int i; int maxp = region_maxworkers(r); int rp = rpeasants(r); int max_immigrants = MAX_IMMIGRATION(maxp - rp); if (volcano_module()) { static int terrain_cache; static const terrain_type *t_volcano; static const terrain_type *t_smoking; if (terrain_changed(&terrain_cache)) { t_volcano = newterrain(T_VOLCANO); t_smoking = newterrain(T_VOLCANO_SMOKING); } if (r->terrain == t_volcano || r->terrain == t_smoking) { max_immigrants = max_immigrants / 10; } } for (i = 0; max_immigrants > 0 && i != MAXDIRECTIONS; i++) { int dir = (turn + 1 + i) % MAXDIRECTIONS; region *rc = rconnect(r, (direction_t)dir); if (rc != NULL && fval(rc->terrain, LAND_REGION)) { int rp2 = rpeasants(rc); int maxp2 = region_maxworkers(rc); int max_emigration = MAX_EMIGRATION(rp2 - maxp2); if (max_emigration > 0) { if (max_emigration > max_immigrants) max_emigration = max_immigrants; if (max_emigration + r->land->newpeasants > USHRT_MAX) { max_emigration = USHRT_MAX - r->land->newpeasants; } if (max_emigration + rc->land->newpeasants > USHRT_MAX) { max_emigration = USHRT_MAX - rc->land->newpeasants; } r->land->newpeasants += (short)max_emigration; rc->land->newpeasants -= (short)max_emigration; max_immigrants -= max_emigration; } } } } /* Vermehrungsrate Bauern in 1/10000. * TODO: Evt. Berechnungsfehler, reale Vermehrungsraten scheinen hoeher. */ #define PEASANTGROWTH 10 #define PEASANTLUCK 10 #define PEASANTFORCE 0.75 /* Chance einer Vermehrung trotz 90% Auslastung */ static double peasant_growth_factor(void) { return config_get_flt("rules.peasants.growth.factor", 0.0001 * (double)PEASANTGROWTH); } static double peasant_luck_factor(void) { return config_get_flt("rules.peasants.peasantluck.factor", PEASANTLUCK); } int peasant_luck_effect(int peasants, int luck, int maxp, double variance) { int births = 0; double mean; if (luck == 0) return 0; mean = fmin(luck, peasants); mean *= peasant_luck_factor() * peasant_growth_factor(); mean *= ((peasants / (double)maxp < .9) ? 1 : PEASANTFORCE); births = RAND_ROUND(normalvariate(mean, variance * mean)); if (births <= 0) births = 1; if (births > peasants / 2) births = peasants / 2 + 1; return births; } static void peasants(region * r, int rule) { int rp = rpeasants(r); int money = rmoney(r); int maxp = max_production(r); int n, satiated; int dead = 0, peasants = rp; if (peasants > 0 && rule > 0) { int luck = 0; double fraction = peasants * peasant_growth_factor(); int births = RAND_ROUND(fraction); attrib *a = a_find(r->attribs, &at_peasantluck); if (a != NULL) { luck = a->data.i * 1000; } luck = peasant_luck_effect(peasants, luck, maxp, .5); if (luck > 0) ADDMSG(&r->msgs, msg_message("peasantluck_success", "births", luck)); peasants += births + luck; } /* Alle werden satt, oder halt soviele fuer die es auch Geld gibt */ satiated = money / maintenance_cost(NULL); if (satiated > peasants) satiated = peasants; rsetmoney(r, money - satiated * maintenance_cost(NULL)); /* Von denjenigen, die nicht satt geworden sind, verhungert der * Grossteil. dead kann nie groesser als rpeasants(r) - satiated werden, * so dass rpeasants(r) >= 0 bleiben muss. */ /* Es verhungert maximal die unterernaehrten Bevoelkerung. */ n = peasants - satiated; if (n > rp) n = rp; dead += (int)(0.5 + n * PEASANT_STARVATION_CHANCE); if (dead > 0) { ADDMSG(&r->msgs, msg_message("phunger", "dead", dead)); peasants -= dead; } rsetpeasants(r, peasants); } /* ------------------------------------------------------------- */ typedef struct migration { struct migration *next; region *r; int horses; int trees; } migration; #define MSIZE 1023 migration *migrants[MSIZE]; migration *free_migrants; static migration *get_migrants(region * r) { int key = reg_hashkey(r); int index = key % MSIZE; migration *m = migrants[index]; while (m && m->r != r) m = m->next; if (m == NULL) { /* Es gibt noch keine Migration. Also eine erzeugen */ m = free_migrants; if (!m) { m = calloc(1, sizeof(migration)); if (!m) abort(); } else { free_migrants = free_migrants->next; m->horses = 0; m->trees = 0; } m->r = r; m->next = migrants[index]; migrants[index] = m; } return m; } static void migrate(region * r) { int key = reg_hashkey(r); int index = key % MSIZE; migration **hp = &migrants[index]; fset(r, RF_MIGRATION); while (*hp && (*hp)->r != r) hp = &(*hp)->next; if (*hp) { migration *m = *hp; rsethorses(r, rhorses(r) + m->horses); /* Was macht das denn hier? * Baumwanderung wird in trees() gemacht. * wer fragt das? Die Baumwanderung war abhaengig von der * Auswertungsreihenfolge der regionen, * das hatte ich geaendert. jemand hat es wieder geloescht, toll. * ich habe es wieder aktiviert, muss getestet werden. */ *hp = m->next; m->next = free_migrants; free_migrants = m; } } static void horses(region * r) { int horses, maxhorses; direction_t n; /* Logistisches Wachstum, Optimum bei halbem Maximalbesatz. */ maxhorses = region_maxworkers(r) / 10; horses = rhorses(r); if (horses > 0) { if (is_cursed(r->attribs, &ct_godcursezone)) { rsethorses(r, (int)(horses * 0.9)); } else if (maxhorses > 0) { double growth = (RESOURCE_QUANTITY * (HORSEGROWTH * 200.0 * ((double)maxhorses - horses))) / (double)maxhorses; if (growth > 0) { int i; if (a_find(r->attribs, &at_horseluck)) { growth *= 2; } /* printf("Horses: <%d> %d -> ", growth, horses); */ i = (int)(0.5 + (horses * 0.0001) * growth); /* printf("%d\n", horses); */ rsethorses(r, horses + i); } } } /* Pferde wandern in Nachbarregionen. * Falls die Nachbarregion noch berechnet * werden muss, wird eine migration-Struktur gebildet, * die dann erst in die Berechnung der Nachbarstruktur einfliesst. */ for (n = 0; n != MAXDIRECTIONS; n++) { region *r2 = rconnect(r, n); if (r2 && fval(r2->terrain, WALK_INTO)) { int pt = (rhorses(r) * HORSEMOVE) / 100; pt = (int)normalvariate(pt, pt / 4.0); if (pt < 0) pt = 0; if (fval(r2, RF_MIGRATION)) rsethorses(r2, rhorses(r2) + pt); else { migration *nb; /* haben wir die Migration schonmal benutzt? * wenn nicht, muessen wir sie suchen. * Wandernde Pferde vermehren sich nicht. */ nb = get_migrants(r2); nb->horses += pt; } /* Wandernde Pferde sollten auch abgezogen werden */ rsethorses(r, rhorses(r) - pt); } } assert(rhorses(r) >= 0); } static int count_race(const region * r, const race * rc) { unit *u; int c = 0; for (u = r->units; u; u = u->next) if (u_race(u) == rc) c += u->number; return c; } attrib_type at_germs = { "germs", DEFAULT_INIT, DEFAULT_FINALIZE, DEFAULT_AGE, a_writeshorts, a_readshorts, NULL, ATF_UNIQUE }; static void growing_trees_e3(region * r, const int current_season, const int last_weeks_season) { static const int transform[4][3] = { { -1, -1, 0 }, { TREE_SEED, TREE_SAPLING, 2 }, { TREE_SAPLING, TREE_TREE, 2 }, { TREE_TREE, TREE_SEED, 2 } }; if (r->land && current_season != last_weeks_season && transform[current_season][2]) { int src_type = transform[current_season][0]; int dst_type = transform[current_season][1]; int src = rtrees(r, src_type); int dst = rtrees(r, dst_type); int grow = src / transform[current_season][2]; if (grow > 0) { if (src_type != TREE_TREE) { rsettrees(r, src_type, src - grow); } rsettrees(r, dst_type, dst + grow); if (dst_type == TREE_SEED && r->terrain->size) { region *rn[MAXDIRECTIONS]; int d; double fgrow = grow / (double)MAXDIRECTIONS; get_neighbours(r, rn); for (d = 0; d != MAXDIRECTIONS; ++d) { region *rx = rn[d]; if (rx && rx->land) { double scale = 1.0; int g; double fg, ch; int seeds = rtrees(rx, dst_type); if (r->terrain->size > rx->terrain->size) { scale = (scale * rx->terrain->size) / r->terrain->size; } fg = scale * fgrow; g = (int)fg; ch = fg - g; if (chance(ch)) ++g; if (g > 0) { rsettrees(rx, dst_type, seeds + g); } } } } } } } static void growing_trees(region * r, const season_t current_season, const season_t last_weeks_season) { int grownup_trees, i, seeds, sprout; attrib *a; if (current_season == SEASON_SUMMER || current_season == SEASON_AUTUMN) { double seedchance = 0.01F * RESOURCE_QUANTITY; int mp, elves = count_race(r, get_race(RC_ELF)); direction_t d; a = a_find(r->attribs, &at_germs); if (a && last_weeks_season == SEASON_SPRING) { /* ungekeimte Samen bleiben erhalten, Sproesslinge wachsen */ sprout = rtrees(r, 1); if (sprout > a->data.sa[1]) sprout = a->data.sa[1]; /* aus dem gesamt Sproesslingepool abziehen */ rsettrees(r, 1, rtrees(r, 1) - sprout); /* zu den Baeumen hinzufuegen */ rsettrees(r, 2, rtrees(r, 2) + sprout); a_removeall(&r->attribs, &at_germs); } if (is_cursed(r->attribs, &ct_godcursezone)) { rsettrees(r, 1, (int)(rtrees(r, 1) * 0.9)); rsettrees(r, 2, (int)(rtrees(r, 2) * 0.9)); return; } mp = max_production(r); if (mp <= 0) return; /* Grundchance 1.0% */ /* Jeder Elf in der Region erhoeht die Chance marginal */ mp = mp / 8; if (elves > mp) elves = mp; if (elves) { seedchance += 1.0 - pow(0.99999, elves * RESOURCE_QUANTITY); } grownup_trees = rtrees(r, 2); seeds = 0; if (grownup_trees > 0) { double remainder = seedchance * grownup_trees; seeds = (int)(remainder); remainder -= seeds; if (chance(remainder)) { ++seeds; } if (seeds > 0) { seeds += rtrees(r, 0); rsettrees(r, 0, seeds); } } /* Baeume breiten sich in Nachbarregionen aus. */ /* Gesamtzahl der Samen: * bis zu 6% (FORESTGROWTH*3) der Baeume samen in die Nachbarregionen */ seeds = (rtrees(r, 2) * FORESTGROWTH * 3) / 1000000; for (d = 0; d != MAXDIRECTIONS; ++d) { region *r2 = rconnect(r, d); if (r2 && fval(r2->terrain, LAND_REGION) && r2->terrain->size) { /* Eine Landregion, wir versuchen Samen zu verteilen: * Die Chance, das Samen ein Stueck Boden finden, in dem sie * keimen koennen, haengt von der Bewuchsdichte und der * verfuegbaren Flaeche ab. In Gletschern gibt es weniger * Moeglichkeiten als in Ebenen. */ sprout = 0; seedchance = (1000.0 * region_maxworkers(r2)) / r2->terrain->size; for (i = 0; i < seeds / MAXDIRECTIONS; i++) { if (rng_int() % 10000 < seedchance) sprout++; } rsettrees(r2, 0, rtrees(r2, 0) + sprout); } } } else if (current_season == SEASON_SPRING) { int growth; if (is_cursed(r->attribs, &ct_godcursezone)) return; /* in at_germs merken uns die Zahl der Samen und Sproesslinge, die * dieses Jahr aelter werden duerfen, damit nicht ein Same im selben * Zyklus zum Baum werden kann */ a = a_find(r->attribs, &at_germs); if (!a) { a = a_add(&r->attribs, a_new(&at_germs)); a->data.sa[0] = (short)rtrees(r, 0); a->data.sa[1] = (short)rtrees(r, 1); } /* wir haben 6 Wochen zum wachsen, jeder Same/Spross hat 18% Chance * zu wachsen, damit sollten nach 5-6 Wochen alle gewachsen sein */ growth = 1800; /* Samenwachstum */ /* Raubbau abfangen, es duerfen nie mehr Samen wachsen, als aktuell * in der Region sind */ seeds = rtrees(r, 0); if (seeds > a->data.sa[0]) seeds = a->data.sa[0]; sprout = 0; for (i = 0; i < seeds; i++) { if (rng_int() % 10000 < growth) sprout++; } /* aus dem Samenpool dieses Jahres abziehen */ a->data.sa[0] = (short)(seeds - sprout); /* aus dem gesamt Samenpool abziehen */ rsettrees(r, 0, rtrees(r, 0) - sprout); /* zu den Sproesslinge hinzufuegen */ rsettrees(r, 1, rtrees(r, 1) + sprout); /* Baumwachstum */ /* hier gehen wir davon aus, das Jungbaeume nicht ohne weiteres aus * der Region entfernt werden koennen, da Jungbaeume in der gleichen * Runde nachwachsen, wir also nicht mehr zwischen diesjaehrigen und * 'alten' Jungbaeumen unterscheiden koennten */ sprout = rtrees(r, 1); if (sprout > a->data.sa[1]) sprout = a->data.sa[1]; grownup_trees = 0; for (i = 0; i < sprout; i++) { if (rng_int() % 10000 < growth) grownup_trees++; } /* aus dem Sproesslingepool dieses Jahres abziehen */ a->data.sa[1] = (short)(sprout - grownup_trees); /* aus dem gesamt Sproesslingepool abziehen */ rsettrees(r, 1, rtrees(r, 1) - grownup_trees); /* zu den Baeumen hinzufuegen */ rsettrees(r, 2, rtrees(r, 2) + grownup_trees); } } static void growing_herbs(region * r, const int current_season, const season_t last_weeks_season) { /* Jetzt die Kraeutervermehrung. Vermehrt wird logistisch: * * Jedes Kraut hat eine Wahrscheinlichkeit von (100-(vorhandene * Kraeuter))% sich zu vermehren. */ UNUSED_ARG(last_weeks_season); if (current_season != SEASON_WINTER) { int i, herbs = rherbs(r); for (i = herbs; i > 0; --i) { if (rng_int() % 100 < (100 - herbs)) { ++herbs; } } rsetherbs(r, herbs); } } void immigration(void) { region *r; int repopulate = config_get_int("rules.economy.repopulate_maximum", 90); log_info(" - Einwanderung..."); for (r = regions; r; r = r->next) { if (r->land && r->land->newpeasants) { int rp = rpeasants(r) + r->land->newpeasants; /* FIXME: kann ernsthaft abs(newpeasants) > rpeasants(r) sein? */ if (rp < 0) rp = 0; rsetpeasants(r, rp); r->land->newpeasants = 0; } /* Genereate some (0-6 depending on the income) peasants out of nothing */ /* if less than 50 are in the region and there is space and no monster or demon units in the region */ if (repopulate) { int peasants = rpeasants(r); int income = wage(r, NULL, NULL, turn) - maintenance_cost(NULL) + 1; if (income >= 0 && r->land && (peasants < repopulate) && region_maxworkers(r) >(peasants + 30) * 2) { int badunit = 0; unit *u; for (u = r->units; u; u = u->next) { if (!playerrace(u_race(u)) || u_race(u) == get_race(RC_DAEMON)) { badunit = 1; break; } } if (badunit == 0) { peasants += (int)(rng_double()*income); rsetpeasants(r, peasants); } } } } } void nmr_warnings(void) { faction *f, *fa; #define HELP_NMR (HELP_GUARD|HELP_MONEY) for (f = factions; f; f = f->next) { if (!fval(f, FFL_NOIDLEOUT) && turn > f->lastorders) { ADDMSG(&f->msgs, msg_message("nmr_warning", "")); if (turn - f->lastorders == NMRTimeout() - 1) { ADDMSG(&f->msgs, msg_message("nmr_warning_final", "")); } if ((turn - f->lastorders) >= 2) { message *msg = NULL; for (fa = factions; fa; fa = fa->next) { int warn = 0; if (config_get_int("rules.alliances", 0) != 0) { if (f->alliance && f->alliance == fa->alliance) { warn = 1; } } else if (alliedfaction(f, fa, HELP_NMR) && alliedfaction(fa, f, HELP_NMR)) { warn = 1; } if (warn) { if (msg == NULL) { msg = msg_message("warn_dropout", "faction turns", f, turn - f->lastorders); } add_message(&fa->msgs, msg); } } if (msg != NULL) msg_release(msg); } } } } void demographics(void) { region *r; int plant_rules = config_get_int("rules.grow.formula", 2); int horse_rules = config_get_int("rules.horses.growth", 1); int peasant_rules = config_get_int("rules.peasants.growth", 1); const struct building_type *bt_harbour = bt_find("harbour"); season_t current_season = calendar_season(turn); season_t last_weeks_season = calendar_season(turn - 1); for (r = regions; r; r = r->next) { ++r->age; /* also oceans. no idea why we didn't always do that */ live(r); if (!fval(r->terrain, SEA_REGION)) { /* die Nachfrage nach Produkten steigt. */ struct demand *dmd; if (r->land) { for (dmd = r->land->demands; dmd; dmd = dmd->next) { if (dmd->value > 0 && dmd->value < MAXDEMAND) { float rise = DMRISE; if (buildingtype_exists(r, bt_harbour, true)) rise = DMRISEHAFEN; if (rng_double() < rise) ++dmd->value; } } /* Seuchen erst nachdem die Bauern sich vermehrt haben * und gewandert sind */ peasant_migration(r); peasants(r, peasant_rules); if (r->age > 20) { double mwp = fmax(region_maxworkers(r), 1); double prob = pow(rpeasants(r) / (mwp * wage(r, NULL, NULL, turn) * 0.13), 4.0) * PLAGUE_CHANCE; if (rng_double() < prob) { plagues(r); } } if (horse_rules > 0) { horses(r); } if (plant_rules == 2) { /* E2 */ growing_trees(r, current_season, last_weeks_season); growing_herbs(r, current_season, last_weeks_season); } else if (plant_rules==1) { /* E3 */ growing_trees_e3(r, current_season, last_weeks_season); } } update_resources(r); if (r->land) migrate(r); } } while (free_migrants) { migration *m = free_migrants->next; free(free_migrants); free_migrants = m; }; remove_empty_units(); immigration(); } int leave_cmd(unit * u, struct order *ord) { region *r = u->region; if (fval(u, UFL_ENTER)) { /* if we just entered this round, then we don't leave again */ return 0; } if (fval(r->terrain, SEA_REGION) && u->ship) { if (!fval(u_race(u), RCF_SWIM)) { cmistake(u, ord, 11, MSG_MOVE); return 0; } if (has_horses(u)) { cmistake(u, ord, 231, MSG_MOVE); return 0; } } leave(u, true); return 0; } void transfer_faction(faction *fsrc, faction *fdst) { unit *u; skill_t sk; int hmax, hnow; int skill_count[MAXSKILLS]; int skill_limit[MAXSKILLS]; assert(fsrc != fdst); for (sk = 0; sk != MAXSKILLS; ++sk) { skill_limit[sk] = faction_skill_limit(fdst, sk); } memset(skill_count, 0, sizeof(skill_count)); for (u = fdst->units; u != NULL; u = u->nextF) { if (u->skills) { int i; for (i = 0; i != u->skill_size; ++i) { const skill *sv = u->skills + i; skill_t sk = (skill_t)sv->id; skill_count[sk] += u->number; } } } hnow = countheroes(fdst); hmax = maxheroes(fdst); u = fsrc->units; while (u) { unit *unext = u->nextF; if (u_race(u) == fdst->race) { if (u->flags & UFL_HERO) { if (u->number + hnow > hmax) { u->flags &= ~UFL_HERO; } else { hnow += u->number; } } if (give_unit_allowed(u) == 0 && !get_mage(u)) { if (u->skills) { int i; for (i = 0; i != u->skill_size; ++i) { const skill *sv = u->skills + i; skill_t sk = (skill_t)sv->id; if (skill_count[sk] + u->number > skill_limit[sk]) { break; } } if (i != u->skill_size) { u = u->nextF; continue; } } ADDMSG(&fdst->msgs, msg_message("transfer_unit", "unit", u)); u_setfaction(u, fdst); } } u = unext; } } int quit_cmd(unit * u, struct order *ord) { char token[128]; faction *f = u->faction; const char *passwd; keyword_t kwd; kwd = init_order_depr(ord); assert(kwd == K_QUIT); passwd = gettoken(token, sizeof(token)); if (checkpasswd(f, (const char *)passwd)) { int flags = FFL_QUIT; if (rule_transfermen()) { param_t p; p = getparam(f->locale); if (p == P_FACTION) { #ifdef QUIT_WITH_TRANSFER faction *f2 = getfaction(); if (f2 == NULL) { cmistake(u, ord, 66, MSG_EVENT); flags = 0; } else if (f->race != f2->race) { cmistake(u, ord, 281, MSG_EVENT); flags = 0; } else { unit *u2; for (u2 = u->region->units; u2; u2 = u2->next) { if (u2->faction == f2) { if (ucontact(u2, u)) { transfer_faction(u->faction, u2->faction); break; } } } if (u2 == NULL) { /* no target unit found */ cmistake(u, ord, 40, MSG_EVENT); flags = 0; } } #else log_error("faction %s: QUIT FACTION is disabled.", factionname(f)); flags = 0; #endif } } f->flags |= flags; } else { char buffer[64]; write_order(ord, f->locale, buffer, sizeof(buffer)); cmistake(u, ord, 86, MSG_EVENT); log_warning("QUIT with illegal password for faction %s: %s\n", itoa36(f->no), buffer); } return 0; } static bool mayenter(region * r, unit * u, building * b) { unit *u2; UNUSED_ARG(r); if (fval(b, BLD_UNGUARDED)) return true; u2 = building_owner(b); if (u2 == NULL || ucontact(u2, u) || alliedunit(u2, u->faction, HELP_GUARD)) return true; return false; } static int mayboard(const unit * u, ship * sh) { unit *u2 = ship_owner(sh); return (!u2 || ucontact(u2, u) || alliedunit(u2, u->faction, HELP_GUARD)); } static bool CheckOverload(void) { return config_get_int("rules.check_overload", 0) != 0; } int enter_ship(unit * u, struct order *ord, int id, bool report) { region *r = u->region; ship *sh; const race * rc = u_race(u); /* Muss abgefangen werden, sonst koennten Schwimmer an * Bord von Schiffen an Land gelangen. */ if (!(rc->flags & (RCF_CANSAIL | RCF_WALK | RCF_FLY))) { if (report) { cmistake(u, ord, 233, MSG_MOVE); } return 0; } sh = findship(id); if (sh == NULL || sh->region != r) { if (report) { cmistake(u, ord, 20, MSG_MOVE); } return 0; } if (sh == u->ship) { return 1; } if (!mayboard(u, sh)) { if (report) { cmistake(u, ord, 34, MSG_MOVE); } return 0; } if (CheckOverload()) { int sweight, scabins; int mweight = ship_capacity(sh); int mcabins = ship_cabins(sh); if (mweight > 0) { getshipweight(sh, &sweight, &scabins); sweight += weight(u); if (mcabins) { int pweight = u->number * u_race(u)->weight; /* weight goes into number of cabins, not cargo */ scabins += pweight; sweight -= pweight; } if (sweight > mweight || (mcabins && (scabins > mcabins))) { if (report) cmistake(u, ord, 34, MSG_MOVE); return 0; } } } if (leave(u, false)) { u_set_ship(u, sh); fset(u, UFL_ENTER); return 1; } else if (report) { cmistake(u, ord, 150, MSG_MOVE); } return 0; } int enter_building(unit * u, order * ord, int id, bool report) { region *r = u->region; building *b; /* Schwimmer koennen keine Gebaeude betreten, ausser diese sind * auf dem Ozean */ if (!fval(u_race(u), RCF_WALK) && !fval(u_race(u), RCF_FLY)) { if (!fval(r->terrain, SEA_REGION)) { if (report) { cmistake(u, ord, 232, MSG_MOVE); } return 0; } } b = findbuilding(id); if (b == NULL || b->region != r) { if (report) { cmistake(u, ord, 6, MSG_MOVE); } return 0; } if (!mayenter(r, u, b)) { if (report) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "entrance_denied", "building", b)); } return 0; } if (leave(u, 0)) { fset(u, UFL_ENTER); u_set_building(u, b); return 1; } else if (report) { cmistake(u, ord, 150, MSG_MOVE); } return 0; } static void do_contact(region * r) { unit * u; for (u = r->units; u; u = u->next) { order *ord; for (ord = u->orders; ord; ord = ord->next) { keyword_t kwd = getkeyword(ord); if (kwd == K_CONTACT) { contact_cmd(u, ord); } } } } void do_enter(struct region *r, bool is_final_attempt) { unit **uptr; for (uptr = &r->units; *uptr;) { unit *u = *uptr; order **ordp = &u->orders; while (*ordp) { order *ord = *ordp; if (getkeyword(ord) == K_ENTER) { char token[128]; param_t p; int id; unit *ulast = NULL; const char * s; init_order_depr(ord); s = gettoken(token, sizeof(token)); p = findparam_ex(s, u->faction->locale); id = getid(); switch (p) { case P_BUILDING: case P_GEBAEUDE: if (u->building && u->building->no == id) break; if (enter_building(u, ord, id, is_final_attempt)) { unit *ub; for (ub = u; ub; ub = ub->next) { if (ub->building == u->building) { ulast = ub; } } } break; case P_SHIP: if (u->ship && u->ship->no == id) break; if (enter_ship(u, ord, id, is_final_attempt)) { unit *ub; ulast = u; for (ub = u; ub; ub = ub->next) { if (ub->ship == u->ship) { ulast = ub; } } } break; default: if (is_final_attempt) { cmistake(u, ord, 79, MSG_MOVE); } } if (ulast != NULL) { /* Wenn wir hier angekommen sind, war der Befehl * erfolgreich und wir loeschen ihn, damit er im * zweiten Versuch nicht nochmal ausgefuehrt wird. */ *ordp = ord->next; ord->next = NULL; free_order(ord); if (ulast != u) { /* put u behind ulast so it's the last unit in the building */ *uptr = u->next; u->next = ulast->next; ulast->next = u; } break; } } if (*ordp == ord) ordp = &ord->next; } if (*uptr == u) uptr = &u->next; } } int dropouts[2]; int *age = NULL; bool nmr_death(const faction * f, int turn, int timeout) { if (f->age >= timeout && turn - f->lastorders >= timeout) { static bool rule_destroy; static int config; if (config_changed(&config)) { rule_destroy = config_get_int("rules.nmr.destroy", 0) != 0; } if (rule_destroy) { unit *u; for (u = f->units; u; u = u->nextF) { if (u->building && building_owner(u->building) == u) { remove_building(&u->region->buildings, u->building); } } } return true; } return false; } static void remove_idle_players(void) { faction **fp; int i, timeout = NMRTimeout(); log_info(" - beseitige Spieler, die sich zu lange nicht mehr gemeldet haben..."); for (fp = &factions; *fp;) { faction *f = *fp; if (timeout > 0 && nmr_death(f, turn, timeout)) { destroyfaction(fp); } else { if (fval(f, FFL_NOIDLEOUT)) { f->lastorders = turn; } else if (turn != f->lastorders) { char info[256]; sprintf(info, "%d Einheiten, %d Personen", f->num_units, f->num_people); } fp = &f->next; } } log_info(" - beseitige Spieler, die sich nach der Anmeldung nicht gemeldet haben..."); i = turn + 1; if (i < 4) i = 4; age = calloc(i, sizeof(int)); if (!age) abort(); for (fp = &factions; *fp;) { faction *f = *fp; if (!is_monsters(f)) { if (RemoveNMRNewbie() && !fval(f, FFL_NOIDLEOUT)) { if (f->age >= 0 && f->age <= turn) ++age[f->age]; if (f->age == 2 || f->age == 3) { if (f->lastorders == turn - 2) { ++dropouts[f->age - 2]; destroyfaction(fp); continue; } } } } fp = &f->next; } } void quit(void) { faction **fptr = &factions; while (*fptr) { faction *f = *fptr; if (f->flags & FFL_QUIT) { destroyfaction(fptr); } else { ++f->age; fptr = &f->next; } } remove_idle_players(); remove_empty_units(); } /* ------------------------------------------------------------- */ /* HELFE partei [] [NICHT] */ int ally_cmd(unit * u, struct order *ord) { char token[128]; struct allies **sfp; faction *f; int keyword, not_kw; const char *s; int sf_status; init_order_depr(ord); f = getfaction(); if (f == NULL || is_monsters(f)) { cmistake(u, ord, 66, MSG_EVENT); return 0; } if (f == u->faction) return 0; s = gettoken(token, sizeof(token)); if (!s || !s[0]) { keyword = P_ANY; } else { keyword = findparam(s, u->faction->locale); } sfp = &u->faction->allies; if (fval(u, UFL_GROUP)) { group *g = get_group(u); if (g) { sfp = &g->allies; } } not_kw = getparam(u->faction->locale); /* HELFE partei [modus] NICHT */ sf_status = ally_get(*sfp, f); switch (keyword) { case P_NOT: sf_status = 0; break; case NOPARAM: cmistake(u, ord, 137, MSG_EVENT); return 0; case P_ANY: sf_status = (not_kw == P_NOT) ? 0 : HELP_ALL; break; case P_TRAVEL: if (not_kw == P_NOT) { sf_status = sf_status & (HELP_ALL - HELP_TRAVEL); } else { sf_status |= HELP_TRAVEL; } break; case P_GIVE: if (not_kw == P_NOT) sf_status &= (HELP_ALL - HELP_GIVE); else sf_status |= HELP_GIVE; break; case P_MONEY: if (not_kw == P_NOT) sf_status &= (HELP_ALL - HELP_MONEY); else sf_status |= HELP_MONEY; break; case P_FIGHT: if (not_kw == P_NOT) sf_status &= (HELP_ALL - HELP_FIGHT); else sf_status |= HELP_FIGHT; break; case P_FACTIONSTEALTH: if (not_kw == P_NOT) sf_status &= (HELP_ALL - HELP_FSTEALTH); else sf_status |= HELP_FSTEALTH; break; case P_GUARD: if (not_kw == P_NOT) sf_status &= (HELP_ALL - HELP_GUARD); else sf_status |= HELP_GUARD; break; } sf_status &= HelpMask(); ally_set(sfp, f, sf_status); return 0; } static struct local_names *pnames; static void init_prefixnames(void) { int i; for (i = 0; localenames[i]; ++i) { const struct locale *lang = get_locale(localenames[i]); bool exist = false; struct local_names *in = pnames; while (in != NULL) { if (in->lang == lang) { exist = true; break; } in = in->next; } if (in == NULL) { in = calloc(1, sizeof(local_names)); if (!in) abort(); } in->next = pnames; in->lang = lang; if (!exist && race_prefixes) { int key; for (key = 0; race_prefixes[key]; ++key) { variant var; const char *pname = LOC(lang, mkname("prefix", race_prefixes[key])); if (findtoken(in->names, pname, &var) == E_TOK_NOMATCH || var.i != key) { var.i = key; addtoken((struct tnode **)&in->names, pname, var); addtoken((struct tnode **)&in->names, LOC(lang, mkname("prefix", race_prefixes[key])), var); } } } pnames = in; } } int prefix_cmd(unit * u, struct order *ord) { char token[128]; attrib **ap; const char *s; local_names *in = pnames; variant var; const struct locale *lang = u->faction->locale; while (in != NULL) { if (in->lang == lang) break; in = in->next; } if (in == NULL) { init_prefixnames(); for (in = pnames; in && in->lang != lang; in = in->next); if (!in) return 0; } init_order_depr(ord); s = gettoken(token, sizeof(token)); if (!s || !*s) { group *g = get_group(u); if (g) { a_removeall(&g->attribs, &at_raceprefix); } else { a_removeall(&u->faction->attribs, &at_raceprefix); } return 0; } else if (findtoken(in->names, s, &var) == E_TOK_NOMATCH) { return 0; } else if (race_prefixes[var.i] == NULL) { cmistake(u, ord, 299, MSG_EVENT); } else { group *g = get_group(u); if (g) { ap = &g->attribs; } else { ap = &u->faction->attribs; } set_prefix(ap, race_prefixes[var.i]); } return 0; } int display_cmd(unit * u, struct order *ord) { char token[128]; char **s = NULL; char *str; region *r = u->region; init_order_depr(ord); str = gettoken(token, sizeof(token)); switch (findparam_ex(str, u->faction->locale)) { case P_BUILDING: case P_GEBAEUDE: if (!u->building) { cmistake(u, ord, 145, MSG_PRODUCE); break; } if (building_owner(u->building) != u) { cmistake(u, ord, 5, MSG_PRODUCE); break; } if (!fval(u->building->type, BTF_NAMECHANGE) && u->building->display && u->building->display[0]) { cmistake(u, ord, 278, MSG_EVENT); break; } s = &u->building->display; break; case P_SHIP: if (!u->ship) { cmistake(u, ord, 144, MSG_PRODUCE); break; } if (ship_owner(u->ship) != u) { cmistake(u, ord, 12, MSG_PRODUCE); break; } s = &u->ship->display; break; case P_UNIT: str = getstrtoken(); if (str) { unicode_utf8_trim(str); } unit_setinfo(u, str); break; case P_PRIVAT: str = getstrtoken(); if (str) { unicode_utf8_trim(str); } usetprivate(u, str); break; case P_REGION: if (!r->land || u->faction != region_get_owner(r)) { cmistake(u, ord, 147, MSG_EVENT); break; } s = &r->land->display; break; default: cmistake(u, ord, 110, MSG_EVENT); break; } if (s != NULL) { const char *s2 = getstrtoken(); free(*s); if (s2) { char * sdup = str_strdup(s2); if (unicode_utf8_trim(sdup) != 0) { log_info("trimming info: %s", s2); } if (strlen(sdup) >= DISPLAYSIZE) { sdup[DISPLAYSIZE-1] = 0; } *s = sdup; } else { *s = NULL; } } return 0; } bool renamed_building(const building * b) { const struct locale *lang = locales; size_t len = strlen(b->name); for (; lang; lang = nextlocale(lang)) { const char *bdname = LOC(lang, b->type->_name); if (bdname) { size_t bdlen = strlen(bdname); if (len >= bdlen && strncmp(b->name, bdname, bdlen) == 0) { return false; } } } return true; } static int rename_cmd(unit * u, order * ord, char **s, const char *s2) { char name[NAMESIZE]; assert(s2); if (!s2[0]) { cmistake(u, ord, 84, MSG_EVENT); return 0; } /* TODO: Validate to make sure people don't have illegal characters in * names, phishing-style? () come to mind. */ str_strlcpy(name, s2, sizeof(name)); if (unicode_utf8_trim(name) != 0) { log_info("trimming name: %s", s2); } free(*s); *s = str_strdup(name); return 0; } static bool try_rename(unit *u, building *b, order *ord) { unit *owner = b ? building_owner(b) : NULL; bool foreign = !(owner && owner->faction == u->faction); if (!b) { cmistake(u, ord, u->building ? 6 : 145, MSG_EVENT); return false; } if (!fval(b->type, BTF_NAMECHANGE) && renamed_building(b)) { cmistake(u, ord, 278, MSG_EVENT); return false; } if (foreign) { if (renamed_building(b)) { cmistake(u, ord, 246, MSG_EVENT); return false; } if (owner) { if (cansee(owner->faction, u->region, u, 0)) { ADDMSG(&owner->faction->msgs, msg_message("renamed_building_seen", "building renamer region", b, u, u->region)); } else { ADDMSG(&owner->faction->msgs, msg_message("renamed_building_notseen", "building region", b, u->region)); } } } if (owner && owner->faction != u->faction) { cmistake(u, ord, 148, MSG_PRODUCE); return false; } return true; } int rename_building(unit * u, order * ord, building * b, const char *name) { assert(name); if (!try_rename(u, b, ord)) { return -1; } return rename_cmd(u, ord, &b->name, name); } int name_cmd(struct unit *u, struct order *ord) { char token[128]; building *b = u->building; region *r = u->region; char **s = NULL; param_t p; bool foreign = false; const char *str; init_order(ord, u->faction->locale); str = gettoken(token, sizeof(token)); p = findparam_ex(str, u->faction->locale); if (p == P_FOREIGN) { str = gettoken(token, sizeof(token)); foreign = true; p = findparam_ex(str, u->faction->locale); } switch (p) { case P_ALLIANCE: if (!foreign && f_get_alliance(u->faction)) { alliance *al = u->faction->alliance; faction *lead = alliance_get_leader(al); if (lead == u->faction) { s = &al->name; } } break; case P_BUILDING: case P_GEBAEUDE: if (foreign) { b = getbuilding(u->region); } if (try_rename(u, b, ord)) { s = &b->name; } break; case P_FACTION: if (foreign) { faction *f; f = getfaction(); if (!f) { cmistake(u, ord, 66, MSG_EVENT); break; } if (f->age < 10) { cmistake(u, ord, 248, MSG_EVENT); break; } else { const struct locale *lang = locales; size_t f_len = strlen(f->name); for (; lang; lang = nextlocale(lang)) { const char *fdname = LOC(lang, "factiondefault"); size_t fdlen = strlen(fdname); if (f_len >= fdlen && strncmp(f->name, fdname, fdlen) == 0) { break; } } if (lang == NULL) { cmistake(u, ord, 247, MSG_EVENT); break; } } if (cansee(f, r, u, 0)) { ADDMSG(&f->msgs, msg_message("renamed_faction_seen", "unit region", u, r)); } else { ADDMSG(&f->msgs, msg_message("renamed_faction_notseen", "", r)); } s = &f->name; } else { s = &u->faction->name; } break; case P_SHIP: if (foreign) { ship *sh = getship(r); unit *uo; if (!sh) { cmistake(u, ord, 20, MSG_EVENT); break; } else { const struct locale *lang = locales; size_t sh_len = strlen(sh->name); for (; lang; lang = nextlocale(lang)) { const char *sdname = LOC(lang, sh->type->_name); size_t sdlen = strlen(sdname); if (sh_len >= sdlen && strncmp(sh->name, sdname, sdlen) == 0) { break; } sdname = LOC(lang, parameters[P_SHIP]); sdlen = strlen(sdname); if (sh_len >= sdlen && strncmp(sh->name, sdname, sdlen) == 0) { break; } } if (lang == NULL) { cmistake(u, ord, 245, MSG_EVENT); break; } } uo = ship_owner(sh); if (uo) { if (cansee(uo->faction, r, u, 0)) { ADDMSG(&uo->faction->msgs, msg_message("renamed_ship_seen", "ship renamer region", sh, u, r)); } else { ADDMSG(&uo->faction->msgs, msg_message("renamed_ship_notseen", "ship region", sh, r)); } } s = &sh->name; } else { unit *uo; if (!u->ship) { cmistake(u, ord, 144, MSG_PRODUCE); break; } uo = ship_owner(u->ship); if (uo->faction != u->faction) { cmistake(u, ord, 12, MSG_PRODUCE); break; } s = &u->ship->name; } break; case P_UNIT: if (foreign) { unit *u2 = 0; getunit(r, u->faction, &u2); if (!u2 || !cansee(u->faction, r, u2, 0)) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", "")); break; } else { char udefault[32]; default_name(u2, udefault, sizeof(udefault)); if (strcmp(unit_getname(u2), udefault) != 0) { cmistake(u, ord, 244, MSG_EVENT); break; } } if (cansee(u2->faction, r, u, 0)) { ADDMSG(&u2->faction->msgs, msg_message("renamed_seen", "renamer renamed region", u, u2, r)); } else { ADDMSG(&u2->faction->msgs, msg_message("renamed_notseen", "renamed region", u2, r)); } s = &u2->_name; } else { s = &u->_name; } break; case P_REGION: if (u->faction != region_get_owner(r)) { cmistake(u, ord, 147, MSG_EVENT); break; } s = &r->land->name; break; case P_GROUP: { group *g = get_group(u); if (g) { s = &g->name; break; } else { cmistake(u, ord, 109, MSG_EVENT); break; } } break; default: cmistake(u, ord, 109, MSG_EVENT); break; } if (s != NULL) { const char *name = getstrtoken(); if (name) { rename_cmd(u, ord, s, name); } else { cmistake(u, ord, 84, MSG_EVENT); } } return 0; } /* ------------------------------------------------------------- */ void deliverMail(faction * f, region * r, unit * u, const char *s, unit * receiver) { if (!cansee(f, r, u, 0)) { u = NULL; } if (!receiver) { /* BOTSCHAFT an PARTEI */ ADDMSG(&f->msgs, msg_message("regionmessage", "region sender string", r, u, s)); } else { /* BOTSCHAFT an EINHEIT */ ADDMSG(&f->msgs, msg_message("unitmessage", "region unit sender string", r, receiver, u, s)); } } static void mailunit(region * r, unit * u, int n, struct order *ord, const char *s) { unit *u2 = findunitr(r, n); if (u2 && cansee(u->faction, r, u2, 0)) { deliverMail(u2->faction, r, u, s, u2); /* now done in prepare_mail_cmd */ } else { /* Immer eine Meldung - sonst koennte man so getarnte EHs enttarnen: * keine Meldung -> EH hier. */ ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", "")); } } static void mailfaction(unit * u, int n, struct order *ord, const char *s) { faction *f; f = findfaction(n); if (f && n > 0) deliverMail(f, u->region, u, s, NULL); else cmistake(u, ord, 66, MSG_MESSAGE); } int mail_cmd(unit * u, struct order *ord) { char token[128]; region *r = u->region; unit *u2; const char *s; int n, cont; init_order_depr(ord); s = gettoken(token, sizeof(token)); /* Falls kein Parameter, ist das eine Einheitsnummer; * das Fuellwort "AN" muss wegfallen, da gueltige Nummer! */ do { cont = 0; switch (findparam_ex(s, u->faction->locale)) { case P_REGION: /* koennen alle Einheiten in der Region sehen */ s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 30, MSG_MESSAGE); break; } else { ADDMSG(&r->msgs, msg_message("mail_result", "unit message", u, s)); return 0; } case P_FACTION: n = getid(); for (u2 = r->units; u2; u2 = u2->next) { if (u2->faction->no == n && seefaction(u->faction, r, u2, 0)) { break; } } if (!u2) { cmistake(u, ord, 66, MSG_MESSAGE); break; } s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 30, MSG_MESSAGE); break; } mailfaction(u, n, ord, s); return 0; case P_UNIT: n = getid(); for (u2 = r->units; u2; u2 = u2->next) { if (u2->no == n && cansee(u->faction, r, u2, 0)) { break; } } if (!u2) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", "")); return 0; } s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 30, MSG_MESSAGE); break; } else { mailunit(r, u, n, ord, s); } return 0; case P_BUILDING: case P_GEBAEUDE: { building *b = getbuilding(r); if (!b) { cmistake(u, ord, 6, MSG_MESSAGE); break; } s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 30, MSG_MESSAGE); break; } for (u2 = r->units; u2; u2 = u2->next) freset(u2->faction, FFL_SELECT); for (u2 = r->units; u2; u2 = u2->next) { if (u2->building == b && !fval(u2->faction, FFL_SELECT) && cansee(u->faction, r, u2, 0)) { mailunit(r, u, u2->no, ord, s); fset(u2->faction, FFL_SELECT); } } return 0; } case P_SHIP: { ship *sh = getship(r); if (!sh) { cmistake(u, ord, 20, MSG_MESSAGE); break; } s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 30, MSG_MESSAGE); break; } for (u2 = r->units; u2; u2 = u2->next) freset(u2->faction, FFL_SELECT); for (u2 = r->units; u2; u2 = u2->next) { if (u2->ship == sh && !fval(u2->faction, FFL_SELECT) && cansee(u->faction, r, u2, 0)) { mailunit(r, u, u2->no, ord, s); fset(u2->faction, FFL_SELECT); } } return 0; } default: /* possibly filler token? */ s = getstrtoken(); if (s && *s) cont = 1; break; } } while (cont); cmistake(u, ord, 149, MSG_MESSAGE); return 0; } /* ------------------------------------------------------------- */ int banner_cmd(unit * u, struct order *ord) { const char * s; init_order_depr(ord); s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 125, MSG_EVENT); } else { faction_setbanner(u->faction, s); ADDMSG(&u->faction->msgs, msg_message("changebanner", "value", s)); } return 0; } int email_cmd(unit * u, struct order *ord) { const char *s; init_order_depr(ord); s = getstrtoken(); if (!s || !s[0]) { cmistake(u, ord, 85, MSG_EVENT); } else { faction *f = u->faction; if (check_email(s) == 0) { faction_setemail(f, s); ADDMSG(&f->msgs, msg_message("changemail", "value", faction_getemail(f))); } else { log_error("Invalid email address for faction %s: %s\n", itoa36(f->no), s); ADDMSG(&f->msgs, msg_message("changemail_invalid", "value", s)); } } return 0; } bool password_wellformed(const char *password) { unsigned char *c = (unsigned char *)password; int i; if (!password || password[0]=='\0') { return false; } for (i = 0; c[i] && i != PASSWORD_MAXSIZE; ++i) { if (!isalnum(c[i])) { return false; } } return true; } int password_cmd(unit * u, struct order *ord) { char pwbuf[PASSWORD_MAXSIZE + 1]; const char *s; init_order_depr(ord); pwbuf[PASSWORD_MAXSIZE] = '\n'; s = gettoken(pwbuf, sizeof(pwbuf)); if (pwbuf[PASSWORD_MAXSIZE] == '\0') { cmistake(u, ord, 321, MSG_EVENT); pwbuf[PASSWORD_MAXSIZE - 1] = '\0'; } if (!s || !password_wellformed(s)) { if (s) { cmistake(u, ord, 283, MSG_EVENT); } password_generate(pwbuf, PASSWORD_MAXSIZE); } faction_setpassword(u->faction, password_hash(pwbuf, PASSWORD_DEFAULT)); ADDMSG(&u->faction->msgs, msg_message("changepasswd", "value", pwbuf)); u->faction->flags |= FFL_PWMSG; return 0; } int send_cmd(unit * u, struct order *ord) { char token[128]; const char *s; int option; init_order_depr(ord); s = gettoken(token, sizeof(token)); option = findoption(s, u->faction->locale); if (option == -1) { cmistake(u, ord, 135, MSG_EVENT); } else { if (getparam(u->faction->locale) == P_NOT) { if (option == O_COMPRESS || option == O_BZIP2) { cmistake(u, ord, 305, MSG_EVENT); } else { u->faction->options = u->faction->options & ~(1 << option); } } else { u->faction->options = u->faction->options | (1 << option); if (option == O_COMPRESS) u->faction->options &= ~(1 << O_BZIP2); if (option == O_BZIP2) u->faction->options &= ~(1 << O_COMPRESS); } } return 0; } static void display_potion(unit * u, const item_type * itype) { show_item(u, itype); } static void display_item(unit * u, const item_type * itype) { faction * f = u->faction; const char *name; const char *key; const char *info; name = resourcename(itype->rtype, 0); key = mkname("iteminfo", name); info = locale_getstring(f->locale, key); if (info == NULL) { info = LOC(f->locale, mkname("iteminfo", "no_info")); } ADDMSG(&f->msgs, msg_message("displayitem", "weight item description", itype->weight, itype->rtype, info)); } static void display_race(faction * f, const race * rc) { char buf[2048]; sbstring sbs; sbs_init(&sbs, buf, sizeof(buf)); report_raceinfo(rc, f->locale, &sbs); addmessage(0, f, buf, MSG_EVENT, ML_IMPORTANT); } static void reshow_other(unit * u, struct order *ord, const char *s) { int err = 21; bool found = false; if (s) { const spell *sp = 0; const item_type *itype; const race *rc; /* check if it's an item */ itype = finditemtype(s, u->faction->locale); sp = unit_getspell(u, s, u->faction->locale); rc = findrace(s, u->faction->locale); if (itype) { /* if this is a potion, we need the right alchemy skill */ err = 36; /* we do not have this item? */ if (itype->flags & ITF_POTION) { /* we don't have the item, but it is a potion. do we know it? */ int level = potion_level(itype); if (level > 0 && 2 * level <= effskill(u, SK_ALCHEMY, NULL)) { display_potion(u, itype); found = true; } } else { int i = i_get(u->items, itype); if (i > 0) { found = true; display_item(u, itype); } } } if (sp) { reset_seen_spells(u->faction, sp); found = true; } if (rc && u_race(u) == rc) { display_race(u->faction, rc); found = true; } } if (!found) { cmistake(u, ord, err, MSG_EVENT); } } static void reshow(unit * u, struct order *ord, const char *s, param_t p) { switch (p) { case P_ZAUBER: reset_seen_spells(u->faction, NULL); break; case P_POTIONS: if (!display_potions(u)) { cmistake(u, ord, 285, MSG_EVENT); } break; case NOPARAM: reshow_other(u, ord, s); break; default: cmistake(u, ord, 222, MSG_EVENT); break; } } int promotion_cmd(unit * u, struct order *ord) { const struct resource_type *rsilver = get_resourcetype(R_SILVER); int money, people; if (fval(u, UFL_HERO)) { /* TODO: message "is already a hero" */ return 0; } if (maxheroes(u->faction) < countheroes(u->faction) + u->number) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "heroes_maxed", "max count", maxheroes(u->faction), countheroes(u->faction))); return 0; } if (!valid_race(u->faction, u_race(u))) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "heroes_race", "race", u_race(u))); return 0; } people = u->faction->num_people * u->number; money = get_pooled(u, rsilver, GET_ALL, people); if (people > money) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "heroes_cost", "cost have", people, money)); return 0; } use_pooled(u, rsilver, GET_ALL, people); fset(u, UFL_HERO); ADDMSG(&u->faction->msgs, msg_message("hero_promotion", "unit cost", u, people)); return 0; } int group_cmd(unit * u, struct order *ord) { keyword_t kwd; kwd = init_order_depr(ord); assert(kwd == K_GROUP); join_group(u, getstrtoken()); return 0; } int origin_cmd(unit * u, struct order *ord) { int px, py; init_order_depr(ord); px = getint(); py = getint(); faction_setorigin(u->faction, getplaneid(u->region), px, py); return 0; } int guard_off_cmd(unit * u, struct order *ord) { assert(getkeyword(ord) == K_GUARD); init_order_depr(ord); if (getparam(u->faction->locale) == P_NOT) { setguard(u, false); } return 0; } int reshow_cmd(unit * u, struct order *ord) { char lbuf[64]; const char *s; param_t p = NOPARAM; init_order_depr(ord); s = gettoken(lbuf, sizeof(lbuf)); if (s && isparam(s, u->faction->locale, P_ANY)) { p = getparam(u->faction->locale); s = NULL; } reshow(u, ord, s, p); return 0; } int status_cmd(unit * u, struct order *ord) { char token[128]; const char *s; init_order_depr(ord); s = gettoken(token, sizeof(token)); switch (findparam(s, u->faction->locale)) { case P_NOT: unit_setstatus(u, ST_AVOID); break; case P_BEHIND: unit_setstatus(u, ST_BEHIND); break; case P_FLEE: unit_setstatus(u, ST_FLEE); break; case P_CHICKEN: unit_setstatus(u, ST_CHICKEN); break; case P_AGGRO: unit_setstatus(u, ST_AGGRO); break; case P_VORNE: unit_setstatus(u, ST_FIGHT); break; case P_HELP: if (getparam(u->faction->locale) == P_NOT) { fset(u, UFL_NOAID); } else { freset(u, UFL_NOAID); } break; default: if (s && s[0]) { add_message(&u->faction->msgs, msg_feedback(u, ord, "unknown_status", "")); } else { unit_setstatus(u, ST_FIGHT); } } return 0; } int combatspell_cmd(unit * u, struct order *ord) { char token[128]; const char *s; int level = 0; spell *sp = 0; init_order_depr(ord); s = gettoken(token, sizeof(token)); /* KAMPFZAUBER [NICHT] loescht alle gesetzten Kampfzauber */ if (!s || *s == 0 || findparam(s, u->faction->locale) == P_NOT) { unset_combatspell(u, 0); return 0; } /* Optional: STUFE n */ if (findparam(s, u->faction->locale) == P_LEVEL) { /* Merken, setzen kommt erst spaeter */ level = getuint(); s = gettoken(token, sizeof(token)); } sp = unit_getspell(u, s, u->faction->locale); if (!sp) { cmistake(u, ord, 173, MSG_MAGIC); return 0; } s = gettoken(token, sizeof(token)); if (findparam(s, u->faction->locale) == P_NOT) { /* KAMPFZAUBER "" NICHT loescht diesen speziellen * Kampfzauber */ unset_combatspell(u, sp); return 0; } else { /* KAMPFZAUBER "" setzt diesen Kampfzauber */ /* knowsspell prueft auf ist_magier, ist_spruch, kennt_spruch */ if (!knowsspell(u->region, u, sp)) { /* Fehler 'Spell not found' */ cmistake(u, ord, 173, MSG_MAGIC); } else if (!u_hasspell(u, sp)) { /* Diesen Zauber kennt die Einheit nicht */ cmistake(u, ord, 169, MSG_MAGIC); } else if (!(sp->sptyp & ISCOMBATSPELL)) { /* Diesen Kampfzauber gibt es nicht */ cmistake(u, ord, 171, MSG_MAGIC); } else { set_combatspell(u, sp, ord, level); } } return 0; } int guard_on_cmd(unit * u, struct order *ord) { assert(getkeyword(ord) == K_GUARD); assert(u); assert(u->faction); init_order_depr(ord); /* GUARD NOT is handled in goard_off_cmd earlier in the turn */ if (getparam(u->faction->locale) == P_NOT) { return 0; } if (fval(u, UFL_MOVED)) { cmistake(u, ord, 187, MSG_EVENT); } else if (fval(u_race(u), RCF_ILLUSIONARY)) { cmistake(u, ord, 95, MSG_EVENT); } else { int err = can_start_guarding(u); if (err == E_GUARD_OK) { setguard(u, true); } else if (err == E_GUARD_TERRAIN) { cmistake(u, ord, 2, MSG_EVENT); } else if (err == E_GUARD_UNARMED) { ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "unit_unarmed", "")); } else if (err == E_GUARD_FLEEING) { cmistake(u, ord, 320, MSG_EVENT); } else if (err == E_GUARD_NEWBIE) { cmistake(u, ord, 304, MSG_EVENT); } } return 0; } void sinkships(struct region * r) { ship **shp = &r->ships; while (*shp) { ship *sh = *shp; if (!sh->type->construction || sh->size >= sh->type->construction->maxsize) { if (fval(r->terrain, SEA_REGION)) { if (!ship_crewed(sh)) { /* ship is at sea, but not enough people to control it */ double dmg = config_get_flt("rules.ship.damage.nocrewocean", 0.3); damage_ship(sh, dmg); } } else if (!ship_owner(sh)) { /* any ship lying around without an owner slowly rots */ double dmg = config_get_flt("rules.ship.damage.nocrew", 0.05); damage_ship(sh, dmg); } } if (sh->damage >= sh->size * DAMAGE_SCALE) { sink_ship(sh); remove_ship(shp, sh); } if (*shp == sh) shp = &sh->next; } } void restack_units(void) { region *r; for (r = regions; r; r = r->next) { unit **up = &r->units; bool sorted = false; while (*up) { unit *u = *up; if (!fval(u, UFL_MARK)) { struct order *ord; for (ord = u->orders; ord; ord = ord->next) { if (getkeyword(ord) == K_SORT) { char token[128]; const char *s; param_t p; int id; unit *v; init_order_depr(ord); s = gettoken(token, sizeof(token)); p = findparam(s, u->faction->locale); id = getid(); v = findunit(id); if (!v || v->faction != u->faction || v->region != r) { cmistake(u, ord, 258, MSG_EVENT); } else if (v->building != u->building || v->ship != u->ship) { cmistake(u, ord, 259, MSG_EVENT); } else if (u->building && building_owner(u->building) == u) { cmistake(u, ord, 260, MSG_EVENT); } else if (u->ship && ship_owner(u->ship) == u) { cmistake(u, ord, 260, MSG_EVENT); } else if (v == u) { syntax_error(u, ord); } else { switch (p) { case P_AFTER: *up = u->next; u->next = v->next; v->next = u; fset(u, UFL_MARK); sorted = true; break; case P_BEFORE: if (v->ship && ship_owner(v->ship) == v) { cmistake(v, ord, 261, MSG_EVENT); } else if (v->building && building_owner(v->building) == v) { cmistake(v, ord, 261, MSG_EVENT); } else { unit **vp = &r->units; while (*vp != v) vp = &(*vp)->next; *vp = u; *up = u->next; u->next = v; } fset(u, UFL_MARK); sorted = true; break; default: /* TODO: syntax error message? */ break; } } break; } } } if (u == *up) up = &u->next; } if (sorted) { unit *u; for (u = r->units; u; u = u->next) { freset(u, UFL_MARK); } } } } /* blesses stone circles create an astral protection in the astral region * above the shield, which prevents chaos suction and other spells. * The shield is created when a magician enters the blessed stone circle, * and lasts for as long as his skill level / 2 is, at no mana cost. * * TODO: this would be nicer in a btype->age function, but we don't have it. */ static void age_stonecircle(building *b) { const struct resource_type *rtype = get_resourcetype(R_UNICORN); region *r = b->region; unit *u, *mage = NULL; /* step 1: give unicorns to people in the building, * find out if there's a magician in there. */ for (u = r->units; u; u = u->next) { if (b == u->building && inside_building(u)) { if (!mage && is_mage(u)) { mage = u; } if (rtype) { int n, unicorns = 0; for (n = 0; n != u->number; ++n) { if (chance(0.02)) { i_change(&u->items, rtype->itype, 1); ++unicorns; } if (unicorns) { ADDMSG(&u->faction->msgs, msg_message("scunicorn", "unit amount rtype", u, unicorns, rtype)); } } } } } /* step 2: if there's a magician, and a connection to astral space, create the * curse. */ if (get_astralplane()) { region *rt = r_standard_to_astral(r); if (mage && rt && !fval(rt->terrain, FORBIDDEN_REGION)) { curse *c = get_curse(rt->attribs, &ct_astralblock); if (!c) { int sk = effskill(mage, SK_MAGIC, NULL); if (sk > 0) { int vig = sk; int dur = (sk + 1) / 2; float effect = 100; /* the mage reactivates the circle */ c = create_curse(mage, &rt->attribs, &ct_astralblock, vig, dur, effect, 0); ADDMSG(&r->msgs, msg_message("astralshield_activate", "region unit", r, mage)); } } else { int sk = effskill(mage, SK_MAGIC, NULL); if (c->duration < sk / 2) c->duration = sk / 2; if (c->vigour < sk) c->vigour = sk; } } } } static building *age_building(building * b) { if (is_building_type(b->type, "blessedstonecircle")) { age_stonecircle(b); } a_age(&b->attribs, b); handle_event(b->attribs, "timer", b); return b; } static void age_region(region * r) { a_age(&r->attribs, r); handle_event(r->attribs, "timer", r); if (!r->land) return; morale_update(r); } static void ageing(void) { faction *f; region *r; /* altern spezieller Attribute, die eine Sonderbehandlung brauchen? */ for (r = regions; r; r = r->next) { unit *u; for (u = r->units; u; u = u->next) { /* Goliathwasser */ int i = get_effect(u, oldpotiontype[P_STRONG]); if (i > 0) { if (i > u->number) i = u->number; change_effect(u, oldpotiontype[P_STRONG], - i); } /* Berserkerblut */ i = get_effect(u, oldpotiontype[P_BERSERK]); if (i > 0) { if (i > u->number) i = u->number; change_effect(u, oldpotiontype[P_BERSERK], - i); } if (u->attribs) { curse * c = get_curse(u->attribs, &ct_oldrace); if (c && curse_active(c)) { if (c->duration == 1 && !(c_flags(c) & CURSE_NOAGE)) { u_setrace(u, get_race(curse_geteffect_int(c))); u->irace = NULL; } } } } } /* Borders */ age_borders(); /* Factions */ for (f = factions; f; f = f->next) { a_age(&f->attribs, f); handle_event(f->attribs, "timer", f); } /* Regionen */ for (r = regions; r; r = r->next) { building **bp; unit **up; ship **sp; age_region(r); /* Einheiten */ for (up = &r->units; *up;) { unit *u = *up; a_age(&u->attribs, u); if (u == *up) handle_event(u->attribs, "timer", u); if (u == *up) /*-V581 */ up = &(*up)->next; } /* Schiffe */ for (sp = &r->ships; *sp;) { ship *s = *sp; a_age(&s->attribs, s); if (s == *sp) handle_event(s->attribs, "timer", s); if (s == *sp) /*-V581 */ sp = &(*sp)->next; } /* Gebaeude */ for (bp = &r->buildings; *bp;) { building *b = *bp; age_building(b); if (b == *bp) bp = &b->next; } if (rule_region_owners()) { update_owners(r); } } } static int maxunits(const faction * f) { int flimit = rule_faction_limit(); int alimit = rule_alliance_limit(); UNUSED_ARG(f); if (alimit == 0) { return flimit; } if (flimit == 0) { return alimit; } return (alimit > flimit) ? flimit : alimit; } int checkunitnumber(const faction * f, int add) { int alimit, flimit; int fno = f->num_units + add; flimit = rule_faction_limit(); if (flimit && fno > flimit) { return 2; } alimit = rule_alliance_limit(); if (alimit) { int unitsinalliance = alliance_size(f->alliance); if (unitsinalliance + add > alimit) { return 1; } } return 0; } void maketemp_cmd(unit *u, order **olist) { order *makeord; int err = checkunitnumber(u->faction, 1); makeord = *olist; if (err) { if (err == 1) { ADDMSG(&u->faction->msgs, msg_feedback(u, makeord, "too_many_units_in_alliance", "allowed", maxunits(u->faction))); } else { ADDMSG(&u->faction->msgs, msg_feedback(u, makeord, "too_many_units_in_faction", "allowed", maxunits(u->faction))); } *olist = makeord->next; makeord->next = NULL; free_order(makeord); while (*olist) { keyword_t kwd; order * ord = *olist; *olist = ord->next; ord->next = NULL; kwd = getkeyword(ord); free_order(ord); if (kwd == K_END) { break; } } } else { char token[128]; const char *s; int alias; ship *sh; unit *u2; order **ordp, **oinsert; #ifndef NDEBUG keyword_t kwd = init_order_depr(makeord); assert(kwd == K_MAKETEMP); #endif alias = getid(); s = gettoken(token, sizeof(token)); if (s && s[0] == '\0') { /* empty name? => generate one */ s = NULL; } u2 = create_unit(u->region, u->faction, 0, u->faction->race, alias, s, u); fset(u2, UFL_ISNEW); usetalias(u2, alias); sh = leftship(u); if (sh) { set_leftship(u2, sh); } unit_setstatus(u2, u->status); /* copy orders until K_END from u to u2 */ ordp = &makeord->next; oinsert = &u2->orders; while (*ordp) { order *ord = *ordp; *ordp = ord->next; if (getkeyword(ord) == K_END) { ord->next = NULL; free_order(ord); break; } *oinsert = ord; oinsert = &ord->next; *oinsert = NULL; } *olist = *ordp; makeord->next = NULL; free_order(makeord); if (!u2->orders) { order *deford = default_order(u2->faction->locale); if (deford) { set_order(&u2->thisorder, NULL); unit_addorder(u2, deford); } } } } void new_units(void) { region *r; unit *u; /* neue einheiten werden gemacht und ihre befehle (bis zum "ende" zu * ihnen rueberkopiert, damit diese einheiten genauso wie die alten * einheiten verwendet werden koennen. */ for (r = regions; r; r = r->next) { for (u = r->units; u; u = u->next) { order **ordp = &u->orders; /* this needs to happen very early in the game somewhere. since this is ** pretty much the first function called per turn, and I am lazy, I ** decree that it goes here */ if (u->flags & UFL_GUARD) { fset(r, RF_GUARDED); } while (*ordp) { order *ord = *ordp; if (getkeyword(ord) == K_MAKETEMP) { maketemp_cmd(u, ordp); } else { ordp = &ord->next; } } } } } void update_long_order(unit * u) { order *ord; bool exclusive = true; keyword_t thiskwd = NOKEYWORD; bool hunger = LongHunger(u); freset(u, UFL_MOVED); freset(u, UFL_LONGACTION); /* check all orders for a potential new long order this round: */ for (ord = u->orders; ord; ord = ord->next) { keyword_t kwd = getkeyword(ord); if (kwd == NOKEYWORD) continue; if (u->old_orders && is_repeated(kwd)) { /* this new order will replace the old defaults */ free_orders(&u->old_orders); } /* hungry units do not get long orders: */ if (hunger) { if (u->old_orders) { /* keep looking for repeated orders that might clear the old_orders */ continue; } break; } if (is_long(kwd)) { if (thiskwd == NOKEYWORD) { /* we have found the (first) long order * some long orders can have multiple instances: */ switch (kwd) { /* Wenn gehandelt wird, darf kein langer Befehl ausgefuehrt * werden. Da Handel erst nach anderen langen Befehlen kommt, * muss das vorher abgefangen werden. Wir merken uns also * hier, ob die Einheit handelt. */ case K_BUY: case K_SELL: case K_CAST: /* non-exclusive orders can be used with others. BUY can be paired with SELL, * CAST with other CAST orders. compatibility is checked once the second * long order is analyzed (below). */ exclusive = false; break; default: set_order(&u->thisorder, copy_order(ord)); break; } thiskwd = kwd; } else { /* we have found a second long order. this is okay for some, but not all commands. * u->thisorder is already set, and should not have to be updated. */ switch (kwd) { case K_CAST: if (thiskwd != K_CAST) { cmistake(u, ord, 52, MSG_EVENT); } break; case K_SELL: if (thiskwd != K_SELL && thiskwd != K_BUY) { cmistake(u, ord, 52, MSG_EVENT); } break; case K_BUY: if (thiskwd != K_SELL) { cmistake(u, ord, 52, MSG_EVENT); } else { thiskwd = K_BUY; } break; default: if (kwd > thiskwd) { /* swap out thisorder for the new one */ cmistake(u, u->thisorder, 52, MSG_EVENT); set_order(&u->thisorder, copy_order(ord)); } else { cmistake(u, ord, 52, MSG_EVENT); } break; } } } } if (hunger) { /* Hungernde Einheiten fuehren NUR den default-Befehl aus */ set_order(&u->thisorder, default_order(u->faction->locale)); } else if (!exclusive) { /* Wenn die Einheit handelt oder zaubert, muss der Default-Befehl geloescht werden. */ set_order(&u->thisorder, NULL); } } static int use_item(unit * u, const item_type * itype, int amount, struct order *ord) { int i; i = get_pooled(u, itype->rtype, GET_DEFAULT, amount); if (amount > i) { /* TODO: message? eg. "not enough %, using only %" */ amount = i; } if (amount == 0) { return ENOITEM; } if (itype->flags & ITF_CANUSE) { int result = callbacks.use_item(u, itype, amount, ord); if (result > 0) { use_pooled(u, itype->rtype, GET_DEFAULT, result); } return result; } return EUNUSABLE; } void monthly_healing(void) { region *r; for (r = regions; r; r = r->next) { unit *u; double healingcurse = 0; if (r->attribs) { /* bonus zuruecksetzen */ curse *c = get_curse(r->attribs, &ct_healing); if (c != NULL) { healingcurse = curse_geteffect(c); } } for (u = r->units; u; u = u->next) { int umhp = unit_max_hp(u) * u->number; double p = 1.0; /* hp ueber Maximum bauen sich ab. Wird zb durch Elixier der Macht * oder veraendertes Ausdauertalent verursacht */ if (u->hp > umhp) { int diff = u->hp - umhp; u->hp -= (int)ceil(diff / 2.0); if (u->hp < umhp) { u->hp = umhp; } continue; } if (u_race(u)->flags & RCF_NOHEAL) continue; if (fval(u, UFL_HUNGER)) continue; if (fval(r->terrain, SEA_REGION) && u->ship == NULL && !(canswim(u) || canfly(u))) { continue; } p *= u_heal_factor(u); if (u->hp < umhp) { double maxheal = fmax(u->number, umhp / 20.0); int addhp; if (active_building(u, bt_find("inn"))) { p *= 1.5; } /* pro punkt 5% hoeher */ p *= (1.0 + healingcurse * 0.05); maxheal = p * maxheal; addhp = (int)maxheal; maxheal -= addhp; if (maxheal > 0.0 && chance(maxheal)) ++addhp; /* Aufaddieren der geheilten HP. */ if (umhp > u->hp + addhp) umhp = u->hp + addhp; u->hp = umhp; /* soll man an negativer regeneration sterben koennen? */ assert(u->hp > 0); } } } } static void remove_exclusive(order ** ordp) { while (*ordp) { order *ord = *ordp; if (is_exclusive(ord)) { *ordp = ord->next; ord->next = NULL; free_order(ord); } else { ordp = &ord->next; } } } void defaultorders(void) { region *r; assert(!keyword_disabled(K_DEFAULT)); for (r = regions; r; r = r->next) { unit *u; for (u = r->units; u; u = u->next) { bool neworders = false; order **ordp = &u->orders; while (*ordp != NULL) { order *ord = *ordp; if (getkeyword(ord) == K_DEFAULT) { char lbuf[8192]; order *new_order = 0; const char *s; init_order_depr(ord); s = gettoken(lbuf, sizeof(lbuf)); if (s) { new_order = parse_order(s, u->faction->locale); } *ordp = ord->next; ord->next = NULL; free_order(ord); if (!neworders) { /* lange Befehle aus orders und old_orders loeschen zu gunsten des neuen */ /* TODO: why only is_exclusive, not is_long? what about CAST, BUY, SELL? */ remove_exclusive(&u->orders); remove_exclusive(&u->old_orders); neworders = true; ordp = &u->orders; /* we could have broken ordp */ } if (new_order) addlist(&u->old_orders, new_order); } else ordp = &ord->next; } } } } /* ************************************************************ */ /* GANZ WICHTIG! ALLE GEAENDERTEN SPRUECHE NEU ANZEIGEN */ /* GANZ WICHTIG! FUEGT AUCH NEUE ZAUBER IN DIE LISTE DER BEKANNTEN EIN */ /* ************************************************************ */ #define COMMONSPELLS 1 /* number of new common spells per level */ #define MAXMAGES 128 /* should be enough */ static int faction_getmages(faction * f, unit ** results, int numresults) { unit *u; int maxlevel = 0, n = 0; for (u = f->units; u; u = u->nextF) { if (u->number > 0) { struct sc_mage * mage = get_mage(u); if (mage) { int level = effskill(u, SK_MAGIC, NULL); if (level > maxlevel) { maxlevel = level; } if (n < numresults) { results[n++] = u; } } } } if (n < numresults) { results[n] = 0; } return maxlevel; } static void copy_spells(const spellbook * src, spellbook * dst, int maxlevel) { assert(dst); if (src && src->spells) { selist *ql; int qi; for (qi = 0, ql = src->spells; ql; selist_advance(&ql, &qi, 1)) { spellbook_entry * sbe = (spellbook_entry *)selist_get(ql, qi); if (sbe->level <= maxlevel) { spell *sp = spellref_get(&sbe->spref); if (!spellbook_get(dst, sp)) { spellbook_add(dst, sp, sbe->level); } } } } } static void show_new_spells(faction * f, int level, const spellbook *book) { if (book) { selist *ql = book->spells; int qi; for (qi = 0; ql; selist_advance(&ql, &qi, 1)) { spellbook_entry *sbe = (spellbook_entry *)selist_get(ql, qi); if (sbe->level <= level) { show_spell(f, sbe); } } } } static void update_spells(void) { faction *f; for (f = factions; f; f = f->next) { if (f->magiegebiet != M_NONE && !is_monsters(f)) { unit *mages[MAXMAGES]; int i; int maxlevel = faction_getmages(f, mages, MAXMAGES); struct spellbook *fsb; if (maxlevel && FactionSpells()) { spellbook * book = get_spellbook(magic_school[f->magiegebiet]); if (!f->spellbook) { f->spellbook = create_spellbook(NULL); } copy_spells(book, f->spellbook, maxlevel); if (maxlevel > f->max_spelllevel) { spellbook * common_spells = get_spellbook(magic_school[M_COMMON]); pick_random_spells(f, maxlevel, common_spells, COMMONSPELLS); } } fsb = faction_get_spellbook(f); show_new_spells(f, maxlevel, fsb); for (i = 0; i != MAXMAGES && mages[i]; ++i) { unit * u = mages[i]; spellbook *sb = unit_get_spellbook(u); if (sb != fsb) { int level = effskill(u, SK_MAGIC, NULL); show_new_spells(f, level, sb); } } } } } int use_cmd(unit * u, struct order *ord) { char token[128]; const char *t; int n, err = ENOITEM; const item_type *itype; init_order_depr(ord); t = gettoken(token, sizeof(token)); if (!t) { cmistake(u, ord, 43, MSG_PRODUCE); return err; } n = atoip((const char *)t); if (n == 0) { if (isparam(t, u->faction->locale, P_ANY)) { /* BENUTZE ALLES Yanxspirit */ n = INT_MAX; t = gettoken(token, sizeof(token)); } else { /* BENUTZE Yanxspirit */ n = 1; } } else { /* BENUTZE 42 Yanxspirit */ t = gettoken(token, sizeof(token)); } itype = t ? finditemtype(t, u->faction->locale) : NULL; if (itype != NULL) { err = use_item(u, itype, n, ord); } switch (err) { case ENOITEM: cmistake(u, ord, 43, MSG_PRODUCE); break; case EUNUSABLE: cmistake(u, ord, 76, MSG_PRODUCE); break; case ENOSKILL: cmistake(u, ord, 50, MSG_PRODUCE); break; default: /* no error */ break; } return err; } int pay_cmd(unit * u, struct order *ord) { if (!u->building) { cmistake(u, ord, 6, MSG_EVENT); } else { param_t p; int id; init_order_depr(ord); p = getparam(u->faction->locale); id = getid(); if (p == P_NOT) { unit *owner = building_owner(u->building); /* If the unit is not the owner of the building: error */ if (owner->no != u->no) { /* The building is not ours error */ cmistake(u, ord, 1222, MSG_EVENT); } else { /* If no building id is given or it is the id of our building, just set the do-not-pay flag */ if (id == 0 || id == u->building->no) { u->building->flags |= BLD_DONTPAY; } else { /* Find the building that matches to the given id*/ building *b = findbuilding(id); /* If there is a building and it is in the same region as the unit continue, else: error */ if (b && b->region == u->region) { /* If the unit is also the building owner (this is true if rules.region_owner_pay_building */ /* is set and no one is in the building) set the do-not-pay flag for this building, else: error */ if (building_owner(b) == u) { b->flags |= BLD_DONTPAY; } else { /* The building is not ours error */ cmistake(u, ord, 1222, MSG_EVENT); } } else { /* Building not found error */ cmistake(u, ord, 6, MSG_PRODUCE); } } } } } return 0; } static int reserve_i(unit * u, struct order *ord, int flags) { if (u->number > 0) { char token[128]; int use, count, para; const item_type *itype; const char *s; init_order_depr(ord); s = gettoken(token, sizeof(token)); count = s ? atoip(s) : 0; para = findparam(s, u->faction->locale); if (count == 0 && para == P_EACH) { count = getint() * u->number; } s = gettoken(token, sizeof(token)); itype = s ? finditemtype(s, u->faction->locale) : 0; if (itype == NULL) return 0; set_resvalue(u, itype, 0); /* make sure the pool is empty */ if (count == 0 && para == P_ANY) { count = get_resource(u, itype->rtype); } use = use_pooled(u, itype->rtype, flags, count); if (use) { set_resvalue(u, itype, use); change_resource(u, itype->rtype, use); return use; } } return 0; } int reserve_cmd(unit * u, struct order *ord) { return reserve_i(u, ord, GET_DEFAULT); } int reserve_self(unit * u, struct order *ord) { return reserve_i(u, ord, GET_RESERVE | GET_SLACK); } int claim_cmd(unit * u, struct order *ord) { char token[128]; const char *t; int n = 1; const item_type *itype = 0; init_order_depr(ord); t = gettoken(token, sizeof(token)); if (t) { n = atoip((const char *)t); if (n == 0) { n = 1; } else { t = gettoken(token, sizeof(token)); } if (t) { itype = finditemtype(t, u->faction->locale); } } if (itype) { item **iclaim = i_find(&u->faction->items, itype); if (iclaim && *iclaim) { if (n > (*iclaim)->number) n = (*iclaim)->number; i_change(iclaim, itype, -n); i_change(&u->items, itype, n); } } else { cmistake(u, ord, 43, MSG_PRODUCE); } return 0; } enum { PROC_THISORDER = 1 << 0, PROC_LONGORDER = 1 << 1 }; typedef enum processor_t { PR_GLOBAL, PR_REGION_PRE, PR_UNIT, PR_ORDER, PR_REGION_POST } processor_t; typedef struct processor { struct processor *next; int priority; processor_t type; unsigned int flags; union { struct { keyword_t kword; int(*process) (struct unit *, struct order *); } per_order; struct { void(*process) (struct unit *); } per_unit; struct { void(*process) (struct region *); } per_region; struct { void(*process) (void); } global; } data; const char *name; } processor; static processor *processors; static processor *add_proc(int priority, const char *name, processor_t type) { processor **pproc = &processors; processor *proc; while (*pproc) { proc = *pproc; if (proc->priority > priority) break; else if (proc->priority == priority && proc->type >= type) break; pproc = &proc->next; } proc = (processor *)malloc(sizeof(processor)); if (!proc) abort(); proc->priority = priority; proc->type = type; proc->name = name; proc->next = *pproc; *pproc = proc; return proc; } void add_proc_order(int priority, keyword_t kword, int(*parser) (struct unit *, struct order *), unsigned int flags, const char *name) { if (!keyword_disabled(kword)) { processor *proc = add_proc(priority, name, PR_ORDER); if (proc) { proc->data.per_order.process = parser; proc->data.per_order.kword = kword; proc->flags = flags; } } } void add_proc_global(int priority, void(*process) (void), const char *name) { processor *proc = add_proc(priority, name, PR_GLOBAL); if (proc) { proc->data.global.process = process; } } void add_proc_region(int priority, void(*process) (region *), const char *name) { processor *proc = add_proc(priority, name, PR_REGION_PRE); if (proc) { proc->data.per_region.process = process; } } void add_proc_postregion(int priority, void(*process) (region *), const char *name) { processor *proc = add_proc(priority, name, PR_REGION_POST); if (proc) { proc->data.per_region.process = process; } } void add_proc_unit(int priority, void(*process) (unit *), const char *name) { processor *proc = add_proc(priority, name, PR_UNIT); if (proc) { proc->data.per_unit.process = process; } } bool long_order_allowed(const unit *u) { const region *r = u->region; if (fval(u, UFL_LONGACTION)) { /* this message was already given in laws.update_long_order cmistake(u, ord, 52, MSG_PRODUCE); */ return false; } else if (fval(r->terrain, SEA_REGION) && u_race(u) != get_race(RC_AQUARIAN) && !(u_race(u)->flags & RCF_SWIM)) { /* error message disabled by popular demand */ return false; } return true; } /* per priority, execute processors in order from PR_GLOBAL down to PR_ORDER */ void process(void) { processor *proc = processors; faction *f; while (proc) { int prio = proc->priority; region *r; processor *pglobal = proc; log_debug("- Step %u", prio); while (proc && proc->priority == prio) { if (proc->name) log_debug(" - %s", proc->name); proc = proc->next; } while (pglobal && pglobal->priority == prio && pglobal->type == PR_GLOBAL) { pglobal->data.global.process(); pglobal = pglobal->next; } if (pglobal == NULL || pglobal->priority != prio) continue; for (r = regions; r; r = r->next) { unit *u; processor *pregion = pglobal; while (pregion && pregion->priority == prio && pregion->type == PR_REGION_PRE) { pregion->data.per_region.process(r); pregion = pregion->next; } if (pregion == NULL || pregion->priority != prio) continue; if (r->units) { for (u = r->units; u; u = u->next) { processor *porder, *punit = pregion; while (punit && punit->priority == prio && punit->type == PR_UNIT) { punit->data.per_unit.process(u); punit = punit->next; } if (punit == NULL || punit->priority != prio) continue; porder = punit; while (porder && porder->priority == prio && porder->type == PR_ORDER) { order **ordp = &u->orders; if (porder->flags & PROC_THISORDER) ordp = &u->thisorder; while (*ordp) { order *ord = *ordp; if (getkeyword(ord) == porder->data.per_order.kword) { if (porder->flags & PROC_LONGORDER) { if (u->number == 0) { ord = NULL; } else if (u_race(u) == get_race(RC_INSECT) && r_insectstalled(r) && !is_cursed(u->attribs, &ct_insectfur)) { ord = NULL; } else if (LongHunger(u)) { cmistake(u, ord, 224, MSG_MAGIC); ord = NULL; } else if (!long_order_allowed(u)) { ord = NULL; } } if (ord) { porder->data.per_order.process(u, ord); if (!u->orders) { /* GIVE UNIT or QUIT delete all orders of the unit, stop */ break; } } } if (!ord || *ordp == ord) ordp = &(*ordp)->next; } porder = porder->next; } } } while (pregion && pregion->priority == prio && pregion->type != PR_REGION_POST) { pregion = pregion->next; } while (pregion && pregion->priority == prio && pregion->type == PR_REGION_POST) { pregion->data.per_region.process(r); pregion = pregion->next; } if (pregion == NULL || pregion->priority != prio) continue; } } log_debug("\n - Leere Gruppen loeschen...\n"); for (f = factions; f; f = f->next) { group **gp = &f->groups; while (*gp) { group *g = *gp; if (g->members == 0) { *gp = g->next; free_group(g); } else gp = &g->next; } } } int armedmen(const unit * u, bool siege_weapons) { item *itm; int n = 0; if (!(u_race(u)->flags & RCF_NOWEAPONS)) { if (effskill(u, SK_WEAPONLESS, NULL) >= 1) { /* kann ohne waffen bewachen: fuer drachen */ n = u->number; } else { /* alle Waffen werden gezaehlt, und dann wird auf die Anzahl * Personen minimiert */ for (itm = u->items; itm; itm = itm->next) { const weapon_type *wtype = resource2weapon(itm->type->rtype); if (wtype == NULL || (!siege_weapons && (wtype->flags & WTF_SIEGE))) continue; if (effskill(u, wtype->skill, NULL) >= 1) n += itm->number; if (n >= u->number) break; } if (n > u->number) n = u->number; } } return n; } static void enter_1(region * r) { do_enter(r, 0); } static void enter_2(region * r) { do_enter(r, 1); } bool help_enter(unit *uo, unit *u) { return uo->faction == u->faction || alliedunit(uo, u->faction, HELP_GUARD); } static void do_force_leave(region *r) { force_leave(r, NULL); } bool rule_force_leave(int flags) { int rules = config_get_int("rules.owners.force_leave", 0); return (rules&flags) == flags; } void init_processor(void) { int p; while (processors) { processor * next = processors->next; free(processors); processors = next; } p = 10; add_proc_global(p, nmr_warnings, "NMR Warnings"); add_proc_global(p, new_units, "Neue Einheiten erschaffen"); p += 10; add_proc_unit(p, update_long_order, "Langen Befehl aktualisieren"); add_proc_order(p, K_BANNER, banner_cmd, 0, NULL); add_proc_order(p, K_EMAIL, email_cmd, 0, NULL); add_proc_order(p, K_PASSWORD, password_cmd, 0, NULL); add_proc_order(p, K_SEND, send_cmd, 0, NULL); add_proc_order(p, K_GROUP, group_cmd, 0, NULL); p += 10; add_proc_order(p, K_URSPRUNG, origin_cmd, 0, NULL); add_proc_order(p, K_ALLY, ally_cmd, 0, NULL); add_proc_order(p, K_PREFIX, prefix_cmd, 0, NULL); add_proc_order(p, K_SETSTEALTH, setstealth_cmd, 0, NULL); add_proc_order(p, K_STATUS, status_cmd, 0, NULL); add_proc_order(p, K_COMBATSPELL, combatspell_cmd, 0, NULL); add_proc_order(p, K_DISPLAY, display_cmd, 0, NULL); add_proc_order(p, K_NAME, name_cmd, 0, NULL); add_proc_order(p, K_GUARD, guard_off_cmd, 0, NULL); add_proc_order(p, K_RESHOW, reshow_cmd, 0, NULL); if (config_get_int("rules.alliances", 0) == 1) { p += 10; add_proc_global(p, alliance_cmd, NULL); } p += 10; add_proc_region(p, do_contact, "Kontaktieren"); add_proc_order(p, K_MAIL, mail_cmd, 0, "Botschaften"); p += 10; /* all claims must be done before we can USE */ add_proc_region(p, enter_1, "Betreten (1. Versuch)"); /* for GIVE CONTROL */ add_proc_order(p, K_USE, use_cmd, 0, "Benutzen"); add_proc_order(p, K_QUIT, quit_cmd, 0, "Stirb"); p += 10; /* in case it has any effects on alliance victories */ add_proc_order(p, K_GIVE, give_control_cmd, 0, "GIB KOMMANDO"); p += 10; /* in case it has any effects on alliance victories */ add_proc_order(p, K_LEAVE, leave_cmd, 0, "Verlassen"); p += 10; add_proc_region(p, enter_1, "Betreten (2. Versuch)"); /* to allow a buildingowner to enter the castle pre combat */ p += 10; add_proc_global(p, do_battles, "Attackieren"); p += 10; /* can't allow reserve before siege (weapons) */ add_proc_region(p, enter_1, "Betreten (3. Versuch)"); /* to claim a castle after a victory and to be able to DESTROY it in the same turn */ if (config_get_int("rules.reserve.twophase", 0)) { add_proc_order(p, K_RESERVE, reserve_self, 0, "RESERVE (self)"); p += 10; } add_proc_order(p, K_RESERVE, reserve_cmd, 0, "RESERVE (all)"); add_proc_order(p, K_CLAIM, claim_cmd, 0, NULL); add_proc_unit(p, follow_unit, "Folge auf Einheiten setzen"); p += 10; /* rest rng again before economics */ if (rule_force_leave(FORCE_LEAVE_ALL)) { add_proc_region(p, do_force_leave, "kick non-allies out of buildings/ships"); } add_proc_region(p, economics, "Geben, Vergessen"); add_proc_region(p+1, recruit, "Rekrutieren"); add_proc_region(p+2, destroy, "Zerstoeren"); /* all recruitment must be finished before we can calculate * promotion cost of ability */ p += 10; add_proc_global(p, quit, "Sterben"); add_proc_order(p, K_PROMOTION, promotion_cmd, 0, "Heldenbefoerderung"); p += 10; if (!keyword_disabled(K_PAY)) { add_proc_order(p, K_PAY, pay_cmd, 0, "Gebaeudeunterhalt (BEZAHLE NICHT)"); } add_proc_postregion(p, maintain_buildings, "Gebaeudeunterhalt"); if (!keyword_disabled(K_CAST)) { p += 10; add_proc_global(p, magic, "Zaubern"); } p += 10; add_proc_region(p, do_autostudy, "study automation"); add_proc_order(p, K_TEACH, teach_cmd, PROC_THISORDER | PROC_LONGORDER, "Lehren"); p += 10; add_proc_order(p, K_STUDY, study_cmd, PROC_THISORDER | PROC_LONGORDER, "Lernen"); p += 10; add_proc_order(p, K_MAKE, make_cmd, PROC_THISORDER | PROC_LONGORDER, "Produktion"); add_proc_postregion(p, produce, "Arbeiten, Handel, Rekruten"); add_proc_postregion(p, split_allocations, "Produktion II"); p += 10; add_proc_region(p, enter_2, "Betreten (4. Versuch)"); /* Once again after QUIT */ p += 10; add_proc_region(p, sinkships, "Schiffe sinken"); p += 10; add_proc_global(p, movement, "Bewegungen"); if (config_get_int("work.auto", 0)) { p += 10; add_proc_region(p, auto_work, "Arbeiten (auto)"); } p += 10; add_proc_order(p, K_GUARD, guard_on_cmd, 0, "Bewache (an)"); p += 10; add_proc_unit(p, monster_kills_peasants, "Monster fressen und vertreiben Bauern"); p += 10; add_proc_global(p, randomevents, "Zufallsereignisse"); p += 10; add_proc_global(p, monthly_healing, "Regeneration (HP)"); add_proc_global(p, regenerate_aura, "Regeneration (Aura)"); if (!keyword_disabled(K_DEFAULT)) { add_proc_global(p, defaultorders, "Defaults setzen"); } add_proc_global(p, demographics, "Nahrung, Seuchen, Wachstum, Wanderung"); if (!keyword_disabled(K_SORT)) { p += 10; add_proc_global(p, restack_units, "Einheiten sortieren"); } if (!keyword_disabled(K_NUMBER)) { add_proc_order(p, K_NUMBER, renumber_cmd, 0, "Neue Nummern (Einheiten)"); p += 10; add_proc_global(p, renumber_factions, "Neue Nummern"); } } static void reset_game(void) { region *r; faction *f; for (r = regions; r; r = r->next) { unit *u; building *b; r->flags &= RF_SAVEMASK; for (u = r->units; u; u = u->next) { u->flags &= UFL_SAVEMASK; } for (b = r->buildings; b; b = b->next) { b->flags &= BLD_SAVEMASK; } if (r->land && r->land->ownership && r->land->ownership->owner) { faction *owner = r->land->ownership->owner; if (owner == get_monsters()) { /* some compat-fix, i believe. */ owner = update_owners(r); } if (owner) { fset(r, RF_GUARDED); } } } for (f = factions; f; f = f->next) { f->flags &= FFL_SAVEMASK; } } void turn_begin(void) { int handle_start = first_turn(); assert(turn >= 0); if (turn < handle_start) { /* this should only happen during tests */ turn = handle_start; } ++turn; reset_game(); } void turn_process(void) { init_processor(); process(); if (markets_module()) { do_markets(); } } void turn_end(void) { log_info(" - Attribute altern"); ageing(); remove_empty_units(); /* must happen AFTER age, because that would destroy them right away */ if (config_get_int("modules.wormhole", 0)) { wormholes_update(); } /* immer ausfuehren, wenn neue Sprueche dazugekommen sind, oder sich * Beschreibungen geaendert haben */ update_spells(); } /** determine if unit can be seen by faction * @param f -- the observiong faction * @param u -- the unit that is observed * @param r -- the region that u is obesrved from (see below) * @param m -- terrain modifier to stealth * * r kann != u->region sein, wenn es um Durchreisen geht, * oder Zauber (sp_generous, sp_fetchastral). * Es muss auch niemand aus f in der region sein, wenn sie vom Turm * erblickt wird */ bool cansee(const faction * f, const region * r, const unit * u, int modifier) { int stealth, rings; if (u->faction == f || omniscient(f)) { return true; } else if (fval(u_race(u), RCF_INVISIBLE)) { return false; } else if (u->number == 0) { attrib *a = a_find(u->attribs, &at_creator); if (a) { /* u is an empty temporary unit. In this special case we look at the creating unit. */ u = (unit *)a->data.v; } else { return false; } } /* simple visibility, just gotta have a viewer in the region to see 'em */ if (leftship(u) || is_guard(u) || u->building || u->ship) { return true; } rings = invisible(u, NULL); stealth = eff_stealth(u, r) - modifier; unit *u2; for (u2 = r->units; u2; u2 = u2->next) { if (u2->faction == f) { if (rings < u->number || invisible(u, u2) < u->number) { if (skill_enabled(SK_PERCEPTION)) { int observation = effskill(u2, SK_PERCEPTION, NULL); if (observation >= stealth) { return true; } } else { return true; } } } } return (rings <= 0 && stealth <= 0); } bool cansee_unit(const unit * u, const unit * target, int modifier) /* target->region kann != u->region sein, wenn es um durchreisen geht */ { if (fval(u_race(target), RCF_INVISIBLE) || target->number == 0) return false; else if (target->faction == u->faction) return true; else { int n, rings; if (is_guard(target) || target->building || target->ship) { return true; } n = eff_stealth(target, target->region) - modifier; rings = invisible(target, NULL); if (rings == 0 && n <= 0) { return true; } if (rings && invisible(target, u) >= target->number) { return false; } if (skill_enabled(SK_PERCEPTION)) { int o = effskill(u, SK_PERCEPTION, target->region); if (o >= n) { return true; } } else { return true; } } return false; } bool cansee_durchgezogen(const faction * f, const region * r, const unit * u, int modifier) /* r kann != u->region sein, wenn es um durchreisen geht */ /* und es muss niemand aus f in der region sein, wenn sie vom Turm * erblickt wird */ { unit *u2; if (fval(u_race(u), RCF_INVISIBLE) || u->number == 0) return false; else if (u->faction == f) return true; else { int rings, n; if (is_guard(u) || u->building || u->ship) { return true; } n = eff_stealth(u, r) - modifier; rings = invisible(u, NULL); if (rings == 0 && n <= 0) { return true; } for (u2 = r->units; u2; u2 = u2->next) { if (u2->faction == f) { int o; if (rings && invisible(u, u2) >= u->number) continue; o = effskill(u2, SK_PERCEPTION, NULL); if (o >= n) { return true; } } } } return false; } bool seefaction(const faction * f, const region * r, const unit * u, int modifier) { if (((f == u->faction) || !fval(u, UFL_ANON_FACTION)) && cansee(f, r, u, modifier)) return true; return false; }