diff --git a/src/eressea/eressea.vcproj b/src/eressea/eressea.vcproj index c1d4095ab..c1a5099dc 100644 --- a/src/eressea/eressea.vcproj +++ b/src/eressea/eressea.vcproj @@ -306,6 +306,26 @@ /> + + + + + + + + diff --git a/src/eressea/src/bindings.c b/src/eressea/src/bindings.c index 388884c67..05796f20e 100644 --- a/src/eressea/src/bindings.c +++ b/src/eressea/src/bindings.c @@ -1,9 +1,10 @@ #include - +#include #include "spells/shipcurse.h" #include #include +#include #include @@ -19,6 +20,37 @@ tolua_levitate_ship(lua_State * L) return 1; } + +extern void spawn_undead(void); +extern void spawn_dragons(void); +extern void plan_monsters(struct faction * f); + + +static int +tolua_planmonsters(lua_State * L) +{ + faction * f = (faction *)tolua_tousertype(L, 1, get_monsters()); + if (f) { + plan_monsters(f); + } + + return 0; +} + +static int +tolua_spawn_dragons(lua_State * L) +{ + spawn_dragons(); + return 0; +} + +static int +tolua_spawn_undead(lua_State * L) +{ + spawn_undead(); + return 0; +} + void bind_eressea(struct lua_State * L) { @@ -26,6 +58,9 @@ bind_eressea(struct lua_State * L) tolua_beginmodule(L, NULL); { tolua_function(L, TOLUA_CAST "levitate_ship", tolua_levitate_ship); + tolua_function(L, TOLUA_CAST "plan_monsters", tolua_planmonsters); + tolua_function(L, TOLUA_CAST "spawn_undead", tolua_spawn_undead); + tolua_function(L, TOLUA_CAST "spawn_dragons", tolua_spawn_dragons); } tolua_endmodule(L); } diff --git a/src/eressea/src/monsters.c b/src/eressea/src/monsters.c new file mode 100644 index 000000000..c80915ee7 --- /dev/null +++ b/src/eressea/src/monsters.c @@ -0,0 +1,1003 @@ +/* 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.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 + +/* triggers includes */ +#include + +/* attributes includes */ +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +#define MOVECHANCE 25 /* chance fuer bewegung */ +#define DRAGON_RANGE 20 /* Max. Distanz zum nächsten Drachenziel */ +#define MAXILLUSION_TEXTS 3 + +static void +reduce_weight(unit * u) +{ + int capacity, weight = 0; + item ** itmp = &u->items; + int horses = get_resource(u, oldresourcetype[R_HORSE]); + + if (horses > 0) { + horses = MIN(horses, (u->number*2)); + change_resource(u, oldresourcetype[R_HORSE], - horses); + } + + /* 0. ditch any vehicles */ + while (*itmp!=NULL) { + item * itm = *itmp; + const item_type * itype = itm->type; + weight += itm->number*itype->weight; + if (itype->flags & ITF_VEHICLE) { + give_item(itm->number, itm->type, u, NULL, NULL); + } + if (*itmp==itm) itmp=&itm->next; + } + + capacity = walkingcapacity(u); + + /* 1. get rid of anything that isn't silver or really lightweight or helpful in combat */ + for (itmp = &u->items;*itmp && capacity>0;) { + item * itm = *itmp; + const item_type * itype = itm->type; + weight += itm->number*itype->weight; + if (weight>capacity) { + if (itype->weight>=10 && itype->rtype->wtype==0 && itype->rtype->atype==0) { + if (itype->capacity < itype->weight) { + int reduce = MIN(itm->number, -((capacity-weight)/itype->weight)); + give_item(reduce, itm->type, u, NULL, NULL); + weight -= reduce * itype->weight; + } + } + } + if (*itmp==itm) itmp=&itm->next; + } + + for (itmp = &u->items;*itmp && weight>capacity;) { + item * itm = *itmp; + const item_type * itype = itm->type; + weight += itm->number*itype->weight; + if (itype->capacity < itype->weight) { + int reduce = MIN(itm->number, -((capacity-weight)/itype->weight)); + give_item(reduce, itm->type, u, NULL, NULL); + weight -= reduce * itype->weight; + } + if (*itmp==itm) itmp=&itm->next; + } +} + +static order * +monster_attack(unit * u, const unit * target) +{ + if (u->region!=target->region) return NULL; + if (u->faction==target->faction) return NULL; + if (!cansee(u->faction, u->region, target, 0)) return NULL; + if (monster_is_waiting(u)) return NULL; + + return create_order(K_ATTACK, u->faction->locale, "%i", target->no); +} + + +static order * +get_money_for_dragon(region * r, unit * u, int wanted) +{ + unit *u2; + int n; + + /* attackiere bewachende einheiten */ + for (u2 = r->units; u2; u2 = u2->next) { + if (u2 != u && is_guard(u2, GUARD_TAX)) { + order * ord = monster_attack(u, u2); + if (ord) addlist(&u->orders, ord); + } + } + + /* falls genug geld in der region ist, treiben wir steuern ein. */ + if (rmoney(r) >= wanted) { + /* 5% chance, dass der drache aus einer laune raus attackiert */ + if (chance(1.0-u->race->aggression)) { + return create_order(K_TAX, default_locale, NULL); + } + } + + /* falls der drache launisch ist, oder das regionssilber knapp, greift er alle an */ + n = 0; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction != u->faction && cansee(u->faction, r, u2, 0)) { + int m = get_money(u2); + if (m==0 || is_guard(u2, GUARD_TAX)) continue; + else { + order * ord = monster_attack(u, u2); + if (ord) { + addlist(&u->orders, ord); + n += m; + } + } + } + } + + /* falls die einnahmen erreicht werden, bleibt das monster noch eine + * runde hier. */ + if (n + rmoney(r) >= wanted) { + return create_order(K_TAX, default_locale, NULL); + } + + /* wenn wir NULL zurückliefern, macht der drache was anderes, z.b. weggehen */ + return NULL; +} + +static int +all_money(region * r, faction * f) +{ + unit *u; + int m; + + m = rmoney(r); + for (u = r->units; u; u = u->next) { + if (f!=u->faction) { + m += get_money(u); + } + } + return m; +} + +static direction_t +richest_neighbour(region * r, faction * f, int absolut) +{ + + /* m - maximum an Geld, d - Richtung, i - index, t = Geld hier */ + + double m; + double t; + direction_t d = NODIRECTION, i; + + if (absolut == 1 || rpeasants(r) == 0) { + m = (double) all_money(r, f); + } else { + m = (double) all_money(r, f) / (double) rpeasants(r); + } + + /* finde die region mit dem meisten geld */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + region * rn = rconnect(r, i); + if (rn!=NULL && fval(rn->terrain, LAND_REGION)) { + if (absolut == 1 || rpeasants(rn) == 0) { + t = (double) all_money(rn, f); + } else { + t = (double) all_money(rn, f) / (double) rpeasants(rn); + } + + if (t > m) { + m = t; + d = i; + } + } + } + return d; +} + +static boolean +room_for_race_in_region(region *r, const race * rc) +{ + unit *u; + int c = 0; + + for(u=r->units;u;u=u->next) { + if(u->race == rc) c += u->number; + } + + if(c > (rc->splitsize*2)) + return false; + + return true; +} + +static direction_t +random_neighbour(region * r, unit *u) +{ + direction_t i; + region *rc; + int rr, c = 0, c2 = 0; + + /* Nachsehen, wieviele Regionen in Frage kommen */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + rc = rconnect(r, i); + if (rc && can_survive(u, rc)) { + if(room_for_race_in_region(rc, u->race)) { + c++; + } + c2++; + } + } + + if (c == 0) { + if(c2 == 0) { + return NODIRECTION; + } else { + c = c2; + c2 = 0; /* c2 == 0 -> room_for_race nicht beachten */ + } + } + + /* Zufällig eine auswählen */ + + rr = rng_int() % c; + + /* Durchzählen */ + + c = -1; + for (i = 0; i != MAXDIRECTIONS; i++) { + rc = rconnect(r, i); + if (rc && can_survive(u, rc)) { + if(c2 == 0) { + c++; + } else if(room_for_race_in_region(rc, u->race)) { + c++; + } + if (c == rr) return i; + } + } + + assert(1 == 0); /* Bis hierhin sollte er niemals kommen. */ + return NODIRECTION; +} + +static direction_t +treeman_neighbour(region * r) +{ + direction_t i; + int rr; + int c = 0; + + /* Nachsehen, wieviele Regionen in Frage kommen */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + if (rconnect(r, i) + && rterrain(rconnect(r, i)) != T_OCEAN + && rterrain(rconnect(r, i)) != T_GLACIER + && rterrain(rconnect(r, i)) != T_DESERT) { + c++; + } + } + + if (c == 0) { + return NODIRECTION; + } + /* Zufällig eine auswählen */ + + rr = rng_int() % c; + + /* Durchzählen */ + + c = -1; + for (i = 0; i != MAXDIRECTIONS; i++) { + if (rconnect(r, i) + && rterrain(rconnect(r, i)) != T_OCEAN + && rterrain(rconnect(r, i)) != T_GLACIER + && rterrain(rconnect(r, i)) != T_DESERT) { + c++; + if (c == rr) { + return i; + } + } + } + + assert(1 == 0); /* Bis hierhin sollte er niemals kommen. */ + return NODIRECTION; +} + +static order * +monster_move(region * r, unit * u) +{ + direction_t d = NODIRECTION; + + if (monster_is_waiting(u)) return NULL; + switch(old_race(u->race)) { + case RC_FIREDRAGON: + case RC_DRAGON: + case RC_WYRM: + d = richest_neighbour(r, u->faction, 1); + break; + case RC_TREEMAN: + d = treeman_neighbour(r); + break; + default: + d = random_neighbour(r,u); + break; + } + + /* falls kein geld gefunden wird, zufaellig verreisen, aber nicht in + * den ozean */ + + if (d == NODIRECTION) + return NULL; + + reduce_weight(u); + return create_order(K_MOVE, u->faction->locale, "%s", LOC(u->faction->locale, directions[d])); +} + +static int +dragon_affinity_value(region *r, unit *u) +{ + int m = all_money(r, u->faction); + + if(u->race == new_race[RC_FIREDRAGON]) { + return (int)(normalvariate(m,m/2)); + } else { + return (int)(normalvariate(m,m/4)); + } +} + +static attrib * +set_new_dragon_target(unit * u, region * r, int range) +{ + int max_affinity = 0; + region *max_region = NULL; + +#if 1 + region_list * rptr, * rlist = regions_in_range(r, range, allowed_dragon); + for (rptr=rlist;rptr;rptr=rptr->next) { + region * r2 = rptr->data; + int affinity = dragon_affinity_value(r2, u); + if (affinity > max_affinity) { + max_affinity = affinity; + max_region = r2; + } + } + + free_regionlist(rlist); +#else + int tx, ty; + for (tx = r->x - range; tx < r->x + range; tx++) { + for (ty = r->y - range; ty < r->y + range; ty++) { + region * r2; + int x = tx, y = ty; + pnormalize(&x, &y, r->planep); + r2 = findregion(x, y); + if (r2!=NULL) { + int affinity = dragon_affinity_value(r2, u); + if (affinity > max_affinity) { + if (koor_distance (r->x, r->y, x, y) <= range && path_exists(r, r2, range, allowed_dragon)) + { + max_affinity = affinity; + max_region = r2; + } + } + } + } + } +#endif + if (max_region && max_region != r) { + attrib * a = a_find(u->attribs, &at_targetregion); + if (!a) { + a = a_add(&u->attribs, make_targetregion(max_region)); + } else { + a->data.v = max_region; + } + return a; + } + return NULL; +} + +static order * +make_movement_order(unit * u, const region * target, int moves, boolean (*allowed)(const region *, const region *)) +{ + region * r = u->region; + region ** plan; + int bytes, position = 0; + char zOrder[128], * bufp = zOrder; + size_t size = sizeof(zOrder) - 1; + + if (monster_is_waiting(u)) return NULL; + + plan = path_find(r, target, DRAGON_RANGE*5, allowed); + if (plan==NULL) return NULL; + + bytes = (int)strlcpy(bufp, (const char *)LOC(u->faction->locale, keywords[K_MOVE]), size); + if (wrptr(&bufp, &size, bytes)!=0) WARN_STATIC_BUFFER(); + + while (position!=moves && plan[position+1]) { + region * prev = plan[position]; + region * next = plan[++position]; + direction_t dir = reldirection(prev, next); + assert(dir!=NODIRECTION && dir!=D_SPECIAL); + if (size>1) { + *bufp++ = ' '; + --size; + } + bytes = (int)strlcpy(bufp, (const char *)LOC(u->faction->locale, directions[dir]), size); + if (wrptr(&bufp, &size, bytes)!=0) WARN_STATIC_BUFFER(); + } + + *bufp = 0; + return parse_order(zOrder, u->faction->locale); +} + +#ifdef TODO_ALP +static order * +monster_seeks_target(region *r, unit *u) +{ + direction_t d; + unit *target = NULL; + int dist, dist2; + direction_t i; + region *nr; + + /* Das Monster sucht ein bestimmtes Opfer. Welches, steht + * in einer Referenz/attribut + * derzeit gibt es nur den alp + */ + + switch( old_race(u->race) ) { + case RC_ALP: + target = alp_target(u); + break; + default: + assert(!"Seeker-Monster gibt kein Ziel an"); + } + + /* TODO: prüfen, ob target überhaupt noch existiert... */ + if (!target) { + log_error(("Monster '%s' hat kein Ziel!\n", unitname(u))); + return NULL; /* this is a bug workaround! remove!! */ + } + + if(r == target->region ) { /* Wir haben ihn! */ + if (u->race == new_race[RC_ALP]) { + alp_findet_opfer(u, r); + } + else { + assert(!"Seeker-Monster hat keine Aktion fuer Ziel"); + } + return NULL; + } + + /* Simpler Ansatz: Nachbarregion mit gerinster Distanz suchen. + * Sinnvoll momentan nur bei Monstern, die sich nicht um das + * Terrain kümmern. Nebelwände & Co machen derzeit auch nix... + */ + dist2 = distance(r, target->region); + d = NODIRECTION; + for( i = 0; i < MAXDIRECTIONS; i++ ) { + nr = rconnect(r, i); + assert(nr); + dist = distance(nr, target->region); + if( dist < dist2 ) { + dist2 = dist; + d = i; + } + } + assert(d != NODIRECTION ); + + return create_order(K_MOVE, u->faction->locale, "%s", LOC(u->faction->locale, directions[d])); +} +#endif + +static void +monster_attacks(unit * u) +{ + region * r = u->region; + unit * u2; + + for (u2=r->units;u2;u2=u2->next) { + if (u2->faction!=u->faction && chance(0.75)) { + order * ord = monster_attack(u, u2); + if (ord) addlist(&u->orders, ord); + } + } +} + +static const char * +random_growl(void) +{ + switch(rng_int()%5) { + case 0: + return "Groammm"; + case 1: + return "Roaaarrrr"; + case 2: + return "Chhhhhhhhhh"; + case 3: + return "Tschrrrkk"; + case 4: + return "Schhhh"; + } + return ""; +} + +extern struct attrib_type at_direction; + +static order * +monster_learn(unit *u) +{ + int c = 0; + int n; + skill * sv; + const struct locale * lang = u->faction->locale; + + /* Monster lernt ein zufälliges Talent aus allen, in denen es schon + * Lerntage hat. */ + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level>0) ++c; + } + + if(c == 0) return NULL; + + n = rng_int()%c + 1; + c = 0; + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level>0) { + if (++c == n) { + return create_order(K_STUDY, lang, "'%s'", skillname(sv->id, lang)); + } + } + } + return NULL; +} + +static boolean +check_overpopulated(unit *u) +{ + unit *u2; + int c = 0; + + for(u2 = u->region->units; u2; u2 = u2->next) { + if(u2->race == u->race && u != u2) c += u2->number; + } + + if(c > u->race->splitsize * 2) return true; + + return false; +} + +static void +recruit_dracoids(unit * dragon, int size) +{ + faction * f = dragon->faction; + region * r = dragon->region; + const struct item * weapon = NULL; + order * new_order = NULL; + unit *un = createunit(r, f, size, new_race[RC_DRACOID]); + + fset(un, UFL_ISNEW|UFL_MOVED); + + name_unit(un); + change_money(dragon, -un->number * 50); + equip_unit(un, get_equipment("recruited_dracoid")); + + setstatus(un, ST_FIGHT); + for (weapon=un->items;weapon;weapon=weapon->next) { + const weapon_type * wtype = weapon->type->rtype->wtype; + if (wtype && (wtype->flags & WTF_MISSILE)) { + setstatus(un, ST_BEHIND); + } + new_order = create_order(K_STUDY, f->locale, "'%s'", + skillname(weapon->type->rtype->wtype->skill, f->locale)); + } + + if (new_order!=NULL) { + addlist(&un->orders, new_order); + } +} + +static order * +plan_dragon(unit * u) +{ + attrib * ta = a_find(u->attribs, &at_targetregion); + region * r = u->region; + region * tr = NULL; + boolean move = false; + order * long_order = NULL; + + reduce_weight(u); + + if (ta==NULL) { + move |= (r->land==0 || r->land->peasants==0); /* when no peasants, move */ + move |= (r->land==0 || r->land->money==0); /* when no money, move */ + } + move |= chance(0.04); /* 4% chance to change your mind */ + + if (u->race==new_race[RC_WYRM] && !move) { + unit * u2; + for (u2=r->units;u2;u2=u2->next) { + /* wyrme sind einzelgänger */ + if (u2==u) { + /* we do not make room for newcomers, so we don't need to look at them */ + break; + } + if (u2!=u && u2->race==u->race && chance(0.5)) { + move = true; + break; + } + } + } + + if (move) { + /* dragon gets bored and looks for a different place to go */ + ta = set_new_dragon_target(u, u->region, DRAGON_RANGE); + } + else ta = a_find(u->attribs, &at_targetregion); + if (ta!=NULL) { + tr = (region *) ta->data.v; + if (tr==NULL || !path_exists(u->region, tr, DRAGON_RANGE, allowed_dragon)) { + ta = set_new_dragon_target(u, u->region, DRAGON_RANGE); + if (ta) tr = findregion(ta->data.sa[0], ta->data.sa[1]); + } + } + if (tr!=NULL) { + assert(long_order==NULL); + switch(old_race(u->race)) { + case RC_FIREDRAGON: + long_order = make_movement_order(u, tr, 4, allowed_dragon); + break; + case RC_DRAGON: + long_order = make_movement_order(u, tr, 3, allowed_dragon); + break; + case RC_WYRM: + long_order = make_movement_order(u, tr, 1, allowed_dragon); + break; + } + if (rng_int()%100 < 15) { + const struct locale * lang = u->faction->locale; + /* do a growl */ + if (rname(tr, lang)) { + addlist(&u->orders, create_order(K_MAIL, lang, "%s '%s... %s %s %s'", + LOC(lang, parameters[P_REGION]), random_growl(), + u->number==1?"Ich rieche":"Wir riechen", + "etwas in", rname(tr, u->faction->locale))); + } + } + } else { + /* we have no target. do we like it here, then? */ + long_order = get_money_for_dragon(u->region, u, income(u)); + if (long_order==NULL) { + /* money is gone, need a new target */ + set_new_dragon_target(u, u->region, DRAGON_RANGE); + } + else if (u->race != new_race[RC_FIREDRAGON]) { + /* neue dracoiden! */ + if (r->land && !fval(r->terrain, FORBIDDEN_REGION)) { + int ra = 20 + rng_int() % 100; + if (get_money(u) > ra * 50 + 100 && rng_int() % 100 < 50) { + recruit_dracoids(u, ra); + } + } + } + } + if (long_order==NULL) { + skill_t sk = SK_PERCEPTION; + /* study perception (or a random useful skill) */ + while (!skill_enabled[sk] || u->race->bonus[sk]<-5) { + sk = (skill_t)(rng_int() % MAXSKILLS); + } + long_order = create_order(K_STUDY, u->faction->locale, "'%s'", + skillname(sk, u->faction->locale)); + } + return long_order; +} + +void +plan_monsters(faction * f) +{ + region *r; + + assert(f); + f->lastorders = turn; + + for (r = regions; r; r = r->next) { + unit *u; + double attack_chance = MONSTERATTACK; + boolean attacking = false; + + for (u = r->units; u; u = u->next) { + attrib * ta; + order * long_order = NULL; + + /* Ab hier nur noch Befehle für NPC-Einheiten. */ + if (!is_monsters(u->faction)) continue; + + if (attack_chance>0.0) { + if (chance(attack_chance)) attacking = true; + attack_chance = 0.0; + } + + if (u->status>ST_BEHIND) { + setstatus(u, ST_FIGHT); + /* all monsters fight */ + } + if (skill_enabled[SK_PERCEPTION]) { + /* Monster bekommen jede Runde ein paar Tage Wahrnehmung dazu */ + /* TODO: this only works for playerrace */ + produceexp(u, SK_PERCEPTION, u->number); + } + + /* Befehle müssen jede Runde neu gegeben werden: */ + free_orders(&u->orders); + + if (attacking) { + monster_attacks(u); + } + /* units with a plan to kill get ATTACK orders: */ + ta = a_find(u->attribs, &at_hate); + if (ta && !monster_is_waiting(u)) { + unit * tu = (unit *)ta->data.v; + if (tu && tu->region==r) { + addlist(&u->orders, create_order(K_ATTACK, u->faction->locale, "%i", tu->no)); + } else if (tu) { + tu = findunitg(ta->data.i, NULL); + if (tu!=NULL) { + long_order = make_movement_order(u, tu->region, 2, allowed_walk); + } + } + else a_remove(&u->attribs, ta); + } + + /* All monsters guard the region: */ + if (!monster_is_waiting(u) && r->land) { + addlist(&u->orders, create_order(K_GUARD, u->faction->locale, NULL)); + } + + /* Einheiten mit Bewegungsplan kriegen ein NACH: */ + if (long_order==NULL) { + attrib * ta = a_find(u->attribs, &at_targetregion); + if (ta) { + if (u->region == (region*)ta->data.v) { + a_remove(&u->attribs, ta); + } + } else if (u->race->flags & RCF_MOVERANDOM) { + if (rng_int()%100race->bonus[SK_WEAPONLESS] != -99) { + if (eff_skill(u, SK_WEAPONLESS, u->region) < 1) { + long_order = create_order(K_STUDY, f->locale, "'%s'", skillname(SK_WEAPONLESS, f->locale)); + } + } + } + + if (long_order==NULL) { + /* Ab hier noch nicht generalisierte Spezialbehandlungen. */ + + if (!u->orders) { + handle_event(u->attribs, "ai_move", u); + } + + switch (old_race(u->race)) { + case RC_SEASERPENT: + long_order = create_order(K_PIRACY, f->locale, NULL); + break; +#ifdef TODO_ALP + case RC_ALP: + long_order = monster_seeks_target(r, u); + break; +#endif + case RC_FIREDRAGON: + case RC_DRAGON: + case RC_WYRM: + long_order = plan_dragon(u); + break; + default: + if (u->race->flags & RCF_LEARN) { + long_order = monster_learn(u); + } + break; + } + } + if (long_order) { + addlist(&u->orders, long_order); + } + } + } + pathfinder_cleanup(); +} + +static double +chaosfactor(region * r) +{ + attrib * a = a_find(r->attribs, &at_chaoscount); + if (!a) return 0; + return ((double) a->data.i / 1000.0); +} + +static int +nrand(int start, int sub) +{ + int res = 0; + + do { + if (rng_int() % 100 < start) + res++; + start -= sub; + } while (start > 0); + + return res; +} + +/** Drachen und Seeschlangen können entstehen */ +void +spawn_dragons(void) +{ + region * r; + faction * monsters = get_monsters(); + + for (r = regions; r; r = r->next) { + unit * u; + + if (fval(r->terrain, SEA_REGION) && rng_int()%10000 < 1) { + u = createunit(r, monsters, 1, new_race[RC_SEASERPENT]); + fset(u, UFL_ISNEW|UFL_MOVED); + equip_unit(u, get_equipment("monster_seaserpent")); + } + + if ((r->terrain == newterrain(T_GLACIER) || r->terrain == newterrain(T_SWAMP) || r->terrain == newterrain(T_DESERT)) && rng_int() % 10000 < (5 + 100 * chaosfactor(r))) + { + if (chance(0.80)) { + u = createunit(r, monsters, nrand(60, 20) + 1, new_race[RC_FIREDRAGON]); + } else { + u = createunit(r, monsters, nrand(30, 20) + 1, new_race[RC_DRAGON]); + } + fset(u, UFL_ISNEW|UFL_MOVED); + equip_unit(u, get_equipment("monster_dragon")); + + if (verbosity>=2) { + log_printf("%d %s in %s.\n", u->number, + LOC(default_locale, rc_name(u->race, u->number!=1)), regionname(r, NULL)); + } + + name_unit(u); + + /* add message to the region */ + ADDMSG(&r->msgs, + msg_message("sighting", "region race number", r, u->race, u->number)); + } + } +} + +/** Untote können entstehen */ +void +spawn_undead(void) +{ + region * r; + faction * monsters = get_monsters(); + + for (r = regions; r; r = r->next) { + int unburied = deathcount(r); + static const curse_type * ctype = NULL; + + if (!ctype) ctype = ct_find("holyground"); + if (ctype && curse_active(get_curse(r->attribs, ctype))) continue; + + /* Chance 0.1% * chaosfactor */ + if (r->land && unburied > r->land->peasants / 20 && rng_int() % 10000 < (100 + 100 * chaosfactor(r))) { + unit * u; + /* es ist sinnfrei, wenn irgendwo im Wald 3er-Einheiten Untote entstehen. + * Lieber sammeln lassen, bis sie mindestens 5% der Bevölkerung sind, und + * dann erst auferstehen. */ + int undead = unburied / (rng_int() % 2 + 1); + const race * rc = NULL; + int i; + if (r->age<100) undead = undead * r->age / 100; /* newbie-regionen kriegen weniger ab */ + + if (!undead || r->age < 20) continue; + + switch(rng_int()%3) { + case 0: + rc = new_race[RC_SKELETON]; break; + case 1: + rc = new_race[RC_ZOMBIE]; break; + default: + rc = new_race[RC_GHOUL]; break; + } + + u = createunit(r, monsters, undead, rc); + fset(u, UFL_ISNEW|UFL_MOVED); + if ((rc == new_race[RC_SKELETON] || rc == new_race[RC_ZOMBIE]) && rng_int()%10 < 4) { + equip_unit(u, get_equipment("rising_undead")); + } + + for (i=0;i < MAXSKILLS;i++) { + if (rc->bonus[i] >= 1) { + set_level(u, (skill_t)i, 1); + } + } + u->hp = unit_max_hp(u) * u->number; + + deathcounts(r, -undead); + name_unit(u); + + if (verbosity>=2) { + log_printf("%d %s in %s.\n", u->number, + LOC(default_locale, rc_name(u->race, u->number!=1)), regionname(r, NULL)); + } + + { + message * msg = msg_message("undeadrise", "region", r); + add_message(&r->msgs, msg); + for (u=r->units;u;u=u->next) freset(u->faction, FFL_SELECT); + for (u=r->units;u;u=u->next) { + if (fval(u->faction, FFL_SELECT)) continue; + fset(u->faction, FFL_SELECT); + add_message(&u->faction->msgs, msg); + } + msg_release(msg); + } + } else { + int i = deathcount(r); + if (i) { + /* Gräber verwittern, 3% der Untoten finden die ewige Ruhe */ + deathcounts(r, (int)(-i*0.03)); + } + } + } +} diff --git a/src/eressea/src/server.c b/src/eressea/src/server.c index aa13b890d..04103e18b 100644 --- a/src/eressea/src/server.c +++ b/src/eressea/src/server.c @@ -12,3 +12,4 @@ #include "main.c" #include "bindings.c" +#include "monsters.c" diff --git a/src/src/bindings/bindings.c b/src/src/bindings/bindings.c index 3c073085a..0fffa712d 100644 --- a/src/src/bindings/bindings.c +++ b/src/src/bindings/bindings.c @@ -222,49 +222,6 @@ tolua_message_region(lua_State * L) return 0; } -static void -free_script(attrib * a) -{ - lua_State * L = (lua_State *)global.vm_state; - if (a->data.i>0) { - luaL_unref(L, LUA_REGISTRYINDEX, a->data.i); - } -} - -attrib_type at_script = { - "script", - NULL, free_script, NULL, - NULL, NULL, ATF_UNIQUE -}; - -static int -call_script(lua_State * L, struct unit * u) -{ - const attrib * a = a_findc(u->attribs, &at_script); - if (a==NULL) a = a_findc(u->race->attribs, &at_script); - if (a!=NULL && a->data.i>0) { - lua_rawgeti(L, LUA_REGISTRYINDEX, a->data.i); - if (lua_pcall(L, 1, 0, 0)!=0) { - const char* error = lua_tostring(L, -1); - log_error(("call_script (%s): %s", unitname(u), error)); - lua_pop(L, 1); - } - } - return -1; -} - -static void -setscript(lua_State * L, struct attrib ** ap) -{ - attrib * a = a_find(*ap, &at_script); - if (a == NULL) { - a = a_add(ap, a_new(&at_script)); - } else if (a->data.i>0) { - luaL_unref(L, LUA_REGISTRYINDEX, a->data.i); - } - a->data.i = luaL_ref(L, LUA_REGISTRYINDEX); -} - static int tolua_update_guards(lua_State * L) { @@ -480,46 +437,6 @@ tolua_levitate_ship(lua_State * L) } #endif -static int -tolua_set_unitscript(lua_State * L) -{ - struct unit * u = (struct unit *)tolua_tousertype(L, 1, 0); - if (u) { - lua_pushvalue(L, 2); - setscript(L, &u->attribs); - lua_pop(L, 1); - } - return 0; -} - -static int -tolua_set_racescript(lua_State * L) -{ - const char * rcname = tolua_tostring(L, 1, 0); - race * rc = rc_find(rcname); - - if (rc!=NULL) { - lua_pushvalue(L, 2); - setscript(L, &rc->attribs); - lua_pop(L, 1); - } - return 0; -} - -static int -tolua_spawn_dragons(lua_State * L) -{ - spawn_dragons(); - return 0; -} - -static int -tolua_spawn_undead(lua_State * L) -{ - spawn_undead(); - return 0; -} - static int tolua_spawn_braineaters(lua_State * L) { @@ -528,22 +445,6 @@ tolua_spawn_braineaters(lua_State * L) return 0; } -static int -tolua_planmonsters(lua_State * L) -{ - faction * f = get_monsters(); - - if (f!=NULL) { - unit * u; - plan_monsters(); - for (u=f->units;u;u=u->nextF) { - call_script(L, u); - } - } - - return 0; -} - static int tolua_init_reports(lua_State* L) { @@ -1185,13 +1086,7 @@ tolua_eressea_open(lua_State* L) tolua_function(L, TOLUA_CAST "message_region", tolua_message_region); /* scripted monsters */ - tolua_function(L, TOLUA_CAST "plan_monsters", tolua_planmonsters); tolua_function(L, TOLUA_CAST "spawn_braineaters", tolua_spawn_braineaters); - tolua_function(L, TOLUA_CAST "spawn_undead", tolua_spawn_undead); - tolua_function(L, TOLUA_CAST "spawn_dragons", tolua_spawn_dragons); - - tolua_function(L, TOLUA_CAST "set_race_brain", tolua_set_racescript); - tolua_function(L, TOLUA_CAST "set_unit_brain", tolua_set_unitscript); #ifdef TODO_FOSS /* spells and stuff */ diff --git a/src/src/gamecode/laws.c b/src/src/gamecode/laws.c index 8a6d69cca..6a1e6b800 100644 --- a/src/src/gamecode/laws.c +++ b/src/src/gamecode/laws.c @@ -4069,7 +4069,7 @@ init_processor(void) p+=10; add_proc_global(p, &encounters, "Zufallsbegegnungen"); p+=10; - add_proc_unit(p, &monsters_kill_peasants, "Monster fressen und vertreiben Bauern"); + add_proc_unit(p, &monster_kills_peasants, "Monster fressen und vertreiben Bauern"); p+=10; add_proc_global(p, &randomevents, "Zufallsereignisse"); diff --git a/src/src/gamecode/monster.c b/src/src/gamecode/monster.c index 912e8df28..6f54d7070 100644 --- a/src/src/gamecode/monster.c +++ b/src/src/gamecode/monster.c @@ -73,526 +73,13 @@ #define MAXILLUSION_TEXTS 3 -static void -reduce_weight(unit * u) -{ - int capacity, weight = 0; - item ** itmp = &u->items; - int horses = get_resource(u, oldresourcetype[R_HORSE]); - - if (horses > 0) { - horses = MIN(horses, (u->number*2)); - change_resource(u, oldresourcetype[R_HORSE], - horses); - } - - /* 0. ditch any vehicles */ - while (*itmp!=NULL) { - item * itm = *itmp; - const item_type * itype = itm->type; - weight += itm->number*itype->weight; - if (itype->flags & ITF_VEHICLE) { - give_item(itm->number, itm->type, u, NULL, NULL); - } - if (*itmp==itm) itmp=&itm->next; - } - - capacity = walkingcapacity(u); - - /* 1. get rid of anything that isn't silver or really lightweight or helpful in combat */ - for (itmp = &u->items;*itmp && capacity>0;) { - item * itm = *itmp; - const item_type * itype = itm->type; - weight += itm->number*itype->weight; - if (weight>capacity) { - if (itype->weight>=10 && itype->rtype->wtype==0 && itype->rtype->atype==0) { - if (itype->capacity < itype->weight) { - int reduce = MIN(itm->number, -((capacity-weight)/itype->weight)); - give_item(reduce, itm->type, u, NULL, NULL); - weight -= reduce * itype->weight; - } - } - } - if (*itmp==itm) itmp=&itm->next; - } - - for (itmp = &u->items;*itmp && weight>capacity;) { - item * itm = *itmp; - const item_type * itype = itm->type; - weight += itm->number*itype->weight; - if (itype->capacity < itype->weight) { - int reduce = MIN(itm->number, -((capacity-weight)/itype->weight)); - give_item(reduce, itm->type, u, NULL, NULL); - weight -= reduce * itype->weight; - } - if (*itmp==itm) itmp=&itm->next; - } -} - -static boolean -is_waiting(const unit * u) +boolean +monster_is_waiting(const unit * u) { if (fval(u, UFL_ISNEW|UFL_MOVED)) return true; return false; } -static order * -monster_attack(unit * u, const unit * target) -{ - if (u->region!=target->region) return NULL; - if (u->faction==target->faction) return NULL; - if (!cansee(u->faction, u->region, target, 0)) return NULL; - if (is_waiting(u)) return NULL; - - return create_order(K_ATTACK, u->faction->locale, "%i", target->no); -} - - -static order * -get_money_for_dragon(region * r, unit * u, int wanted) -{ - unit *u2; - int n; - - /* attackiere bewachende einheiten */ - for (u2 = r->units; u2; u2 = u2->next) { - if (u2 != u && is_guard(u2, GUARD_TAX)) { - order * ord = monster_attack(u, u2); - if (ord) addlist(&u->orders, ord); - } - } - - /* falls genug geld in der region ist, treiben wir steuern ein. */ - if (rmoney(r) >= wanted) { - /* 5% chance, dass der drache aus einer laune raus attackiert */ - if (chance(1.0-u->race->aggression)) { - return create_order(K_TAX, default_locale, NULL); - } - } - - /* falls der drache launisch ist, oder das regionssilber knapp, greift er alle an */ - n = 0; - for (u2 = r->units; u2; u2 = u2->next) { - if (u2->faction != u->faction && cansee(u->faction, r, u2, 0)) { - int m = get_money(u2); - if (m==0 || is_guard(u2, GUARD_TAX)) continue; - else { - order * ord = monster_attack(u, u2); - if (ord) { - addlist(&u->orders, ord); - n += m; - } - } - } - } - - /* falls die einnahmen erreicht werden, bleibt das monster noch eine - * runde hier. */ - if (n + rmoney(r) >= wanted) { - return create_order(K_TAX, default_locale, NULL); - } - - /* wenn wir NULL zurückliefern, macht der drache was anderes, z.b. weggehen */ - return NULL; -} - -static int -all_money(region * r, faction * f) -{ - unit *u; - int m; - - m = rmoney(r); - for (u = r->units; u; u = u->next) { - if (f!=u->faction) { - m += get_money(u); - } - } - return m; -} - -static direction_t -richest_neighbour(region * r, faction * f, int absolut) -{ - - /* m - maximum an Geld, d - Richtung, i - index, t = Geld hier */ - - double m; - double t; - direction_t d = NODIRECTION, i; - - if (absolut == 1 || rpeasants(r) == 0) { - m = (double) all_money(r, f); - } else { - m = (double) all_money(r, f) / (double) rpeasants(r); - } - - /* finde die region mit dem meisten geld */ - - for (i = 0; i != MAXDIRECTIONS; i++) { - region * rn = rconnect(r, i); - if (rn!=NULL && fval(rn->terrain, LAND_REGION)) { - if (absolut == 1 || rpeasants(rn) == 0) { - t = (double) all_money(rn, f); - } else { - t = (double) all_money(rn, f) / (double) rpeasants(rn); - } - - if (t > m) { - m = t; - d = i; - } - } - } - return d; -} - -static boolean -room_for_race_in_region(region *r, const race * rc) -{ - unit *u; - int c = 0; - - for(u=r->units;u;u=u->next) { - if(u->race == rc) c += u->number; - } - - if(c > (rc->splitsize*2)) - return false; - - return true; -} - -static direction_t -random_neighbour(region * r, unit *u) -{ - direction_t i; - region *rc; - int rr, c = 0, c2 = 0; - - /* Nachsehen, wieviele Regionen in Frage kommen */ - - for (i = 0; i != MAXDIRECTIONS; i++) { - rc = rconnect(r, i); - if (rc && can_survive(u, rc)) { - if(room_for_race_in_region(rc, u->race)) { - c++; - } - c2++; - } - } - - if (c == 0) { - if(c2 == 0) { - return NODIRECTION; - } else { - c = c2; - c2 = 0; /* c2 == 0 -> room_for_race nicht beachten */ - } - } - - /* Zufällig eine auswählen */ - - rr = rng_int() % c; - - /* Durchzählen */ - - c = -1; - for (i = 0; i != MAXDIRECTIONS; i++) { - rc = rconnect(r, i); - if (rc && can_survive(u, rc)) { - if(c2 == 0) { - c++; - } else if(room_for_race_in_region(rc, u->race)) { - c++; - } - if (c == rr) return i; - } - } - - assert(1 == 0); /* Bis hierhin sollte er niemals kommen. */ - return NODIRECTION; -} - -static direction_t -treeman_neighbour(region * r) -{ - direction_t i; - int rr; - int c = 0; - - /* Nachsehen, wieviele Regionen in Frage kommen */ - - for (i = 0; i != MAXDIRECTIONS; i++) { - if (rconnect(r, i) - && rterrain(rconnect(r, i)) != T_OCEAN - && rterrain(rconnect(r, i)) != T_GLACIER - && rterrain(rconnect(r, i)) != T_DESERT) { - c++; - } - } - - if (c == 0) { - return NODIRECTION; - } - /* Zufällig eine auswählen */ - - rr = rng_int() % c; - - /* Durchzählen */ - - c = -1; - for (i = 0; i != MAXDIRECTIONS; i++) { - if (rconnect(r, i) - && rterrain(rconnect(r, i)) != T_OCEAN - && rterrain(rconnect(r, i)) != T_GLACIER - && rterrain(rconnect(r, i)) != T_DESERT) { - c++; - if (c == rr) { - return i; - } - } - } - - assert(1 == 0); /* Bis hierhin sollte er niemals kommen. */ - return NODIRECTION; -} - -static order * -monster_move(region * r, unit * u) -{ - direction_t d = NODIRECTION; - - if (is_waiting(u)) return NULL; - switch(old_race(u->race)) { - case RC_FIREDRAGON: - case RC_DRAGON: - case RC_WYRM: - d = richest_neighbour(r, u->faction, 1); - break; - case RC_TREEMAN: - d = treeman_neighbour(r); - break; - default: - d = random_neighbour(r,u); - break; - } - - /* falls kein geld gefunden wird, zufaellig verreisen, aber nicht in - * den ozean */ - - if (d == NODIRECTION) - return NULL; - - reduce_weight(u); - return create_order(K_MOVE, u->faction->locale, "%s", LOC(u->faction->locale, directions[d])); -} - -/* Wir machen das mal autoconf-style: */ -#ifndef HAVE_DRAND48 -#define drand48() (((double)rng_int()) / RAND_MAX) -#endif - -static int -dragon_affinity_value(region *r, unit *u) -{ - int m = all_money(r, u->faction); - - if(u->race == new_race[RC_FIREDRAGON]) { - return (int)(normalvariate(m,m/2)); - } else { - return (int)(normalvariate(m,m/4)); - } -} - -static attrib * -set_new_dragon_target(unit * u, region * r, int range) -{ - int max_affinity = 0; - region *max_region = NULL; - -#if 1 - region_list * rptr, * rlist = regions_in_range(r, range, allowed_dragon); - for (rptr=rlist;rptr;rptr=rptr->next) { - region * r2 = rptr->data; - int affinity = dragon_affinity_value(r2, u); - if (affinity > max_affinity) { - max_affinity = affinity; - max_region = r2; - } - } - - free_regionlist(rlist); -#else - int tx, ty; - for (tx = r->x - range; tx < r->x + range; tx++) { - for (ty = r->y - range; ty < r->y + range; ty++) { - region * r2; - int x = tx, y = ty; - pnormalize(&x, &y, r->planep); - r2 = findregion(x, y); - if (r2!=NULL) { - int affinity = dragon_affinity_value(r2, u); - if (affinity > max_affinity) { - if (koor_distance (r->x, r->y, x, y) <= range && path_exists(r, r2, range, allowed_dragon)) - { - max_affinity = affinity; - max_region = r2; - } - } - } - } - } -#endif - if (max_region && max_region != r) { - attrib * a = a_find(u->attribs, &at_targetregion); - if (!a) { - a = a_add(&u->attribs, make_targetregion(max_region)); - } else { - a->data.v = max_region; - } - return a; - } - return NULL; -} - -static order * -make_movement_order(unit * u, const region * target, int moves, boolean (*allowed)(const region *, const region *)) -{ - region * r = u->region; - region ** plan; - int bytes, position = 0; - char zOrder[128], * bufp = zOrder; - size_t size = sizeof(zOrder) - 1; - - if (is_waiting(u)) return NULL; - - plan = path_find(r, target, DRAGON_RANGE*5, allowed); - if (plan==NULL) return NULL; - - bytes = (int)strlcpy(bufp, (const char *)LOC(u->faction->locale, keywords[K_MOVE]), size); - if (wrptr(&bufp, &size, bytes)!=0) WARN_STATIC_BUFFER(); - - while (position!=moves && plan[position+1]) { - region * prev = plan[position]; - region * next = plan[++position]; - direction_t dir = reldirection(prev, next); - assert(dir!=NODIRECTION && dir!=D_SPECIAL); - if (size>1) { - *bufp++ = ' '; - --size; - } - bytes = (int)strlcpy(bufp, (const char *)LOC(u->faction->locale, directions[dir]), size); - if (wrptr(&bufp, &size, bytes)!=0) WARN_STATIC_BUFFER(); - } - - *bufp = 0; - return parse_order(zOrder, u->faction->locale); -} - -#ifdef TODO_ALP -static order * -monster_seeks_target(region *r, unit *u) -{ - direction_t d; - unit *target = NULL; - int dist, dist2; - direction_t i; - region *nr; - - /* Das Monster sucht ein bestimmtes Opfer. Welches, steht - * in einer Referenz/attribut - * derzeit gibt es nur den alp - */ - - switch( old_race(u->race) ) { - case RC_ALP: - target = alp_target(u); - break; - default: - assert(!"Seeker-Monster gibt kein Ziel an"); - } - - /* TODO: prüfen, ob target überhaupt noch existiert... */ - if (!target) { - log_error(("Monster '%s' hat kein Ziel!\n", unitname(u))); - return NULL; /* this is a bug workaround! remove!! */ - } - - if(r == target->region ) { /* Wir haben ihn! */ - if (u->race == new_race[RC_ALP]) { - alp_findet_opfer(u, r); - } - else { - assert(!"Seeker-Monster hat keine Aktion fuer Ziel"); - } - return NULL; - } - - /* Simpler Ansatz: Nachbarregion mit gerinster Distanz suchen. - * Sinnvoll momentan nur bei Monstern, die sich nicht um das - * Terrain kümmern. Nebelwände & Co machen derzeit auch nix... - */ - dist2 = distance(r, target->region); - d = NODIRECTION; - for( i = 0; i < MAXDIRECTIONS; i++ ) { - nr = rconnect(r, i); - assert(nr); - dist = distance(nr, target->region); - if( dist < dist2 ) { - dist2 = dist; - d = i; - } - } - assert(d != NODIRECTION ); - - return create_order(K_MOVE, u->faction->locale, "%s", LOC(u->faction->locale, directions[d])); -} -#endif - -unit * -random_unit(const region * r) -{ - int c = 0; - int n; - unit *u; - - for (u = r->units; u; u = u->next) { - if (u->race != new_race[RC_SPELL]) { - c += u->number; - } - } - - if (c == 0) { - return NULL; - } - n = rng_int() % c; - c = 0; - u = r->units; - - while (u && c < n) { - if (u->race != new_race[RC_SPELL]) { - c += u->number; - } - u = u->next; - } - - return u; -} - -static void -monster_attacks(unit * u) -{ - region * r = u->region; - unit * u2; - - for (u2=r->units;u2;u2=u2->next) { - if (u2->faction!=u->faction && chance(0.75)) { - order * ord = monster_attack(u, u2); - if (ord) addlist(&u->orders, ord); - } - } -} - static void eaten_by_monster(unit * u) { @@ -642,144 +129,94 @@ eaten_by_monster(unit * u) static void absorbed_by_monster(unit * u) { - int n; + int n; - switch (old_race(u->race)) { - default: - n = rng_int()%(u->number/20+1); - } + switch (old_race(u->race)) { + default: + n = rng_int()%(u->number/20+1); + } - if(n > 0) { - n = lovar(n); - n = MIN(rpeasants(u->region), n); - if (n > 0){ - rsetpeasants(u->region, rpeasants(u->region) - n); - scale_number(u, u->number + n); - ADDMSG(&u->region->msgs, msg_message("absorbpeasants", + if(n > 0) { + n = lovar(n); + n = MIN(rpeasants(u->region), n); + if (n > 0){ + rsetpeasants(u->region, rpeasants(u->region) - n); + scale_number(u, u->number + n); + ADDMSG(&u->region->msgs, msg_message("absorbpeasants", "unit race amount", u, u->race, n)); - } - } + } + } } static int scareaway(region * r, int anzahl) { - int n, p, diff = 0, emigrants[MAXDIRECTIONS]; - direction_t d; + int n, p, diff = 0, emigrants[MAXDIRECTIONS]; + direction_t d; - anzahl = MIN(MAX(1, anzahl),rpeasants(r)); + anzahl = MIN(MAX(1, anzahl),rpeasants(r)); - /* Wandern am Ende der Woche (normal) oder wegen Monster. Die - * Wanderung wird erst am Ende von demographics () ausgefuehrt. - * emigrants[] ist local, weil r->newpeasants durch die Monster - * vielleicht schon hochgezaehlt worden ist. */ + /* Wandern am Ende der Woche (normal) oder wegen Monster. Die + * Wanderung wird erst am Ende von demographics () ausgefuehrt. + * emigrants[] ist local, weil r->newpeasants durch die Monster + * vielleicht schon hochgezaehlt worden ist. */ - for (d = 0; d != MAXDIRECTIONS; d++) - emigrants[d] = 0; + for (d = 0; d != MAXDIRECTIONS; d++) + emigrants[d] = 0; - p = rpeasants(r); - assert(p >= 0 && anzahl >= 0); - for (n = MIN(p, anzahl); n; n--) { - direction_t dir = (direction_t)(rng_int() % MAXDIRECTIONS); - region * rc = rconnect(r, dir); + p = rpeasants(r); + assert(p >= 0 && anzahl >= 0); + for (n = MIN(p, anzahl); n; n--) { + direction_t dir = (direction_t)(rng_int() % MAXDIRECTIONS); + region * rc = rconnect(r, dir); - if (rc && fval(rc->terrain, LAND_REGION)) { - ++diff; - rc->land->newpeasants++; - emigrants[dir]++; - } - } - rsetpeasants(r, p-diff); - assert(p >= diff); - return diff; + if (rc && fval(rc->terrain, LAND_REGION)) { + ++diff; + rc->land->newpeasants++; + emigrants[dir]++; + } + } + rsetpeasants(r, p-diff); + assert(p >= diff); + return diff; } static void scared_by_monster(unit * u) { - int n; + int n; - switch (old_race(u->race)) { - case RC_FIREDRAGON: - n = rng_int()%160 * u->number; - break; - case RC_DRAGON: - n = rng_int()%400 * u->number; - break; - case RC_WYRM: - n = rng_int()%1000 * u->number; - break; - default: - n = rng_int()%(u->number/4+1); - } + switch (old_race(u->race)) { + case RC_FIREDRAGON: + n = rng_int()%160 * u->number; + break; + case RC_DRAGON: + n = rng_int()%400 * u->number; + break; + case RC_WYRM: + n = rng_int()%1000 * u->number; + break; + default: + n = rng_int()%(u->number/4+1); + } - if(n > 0) { - n = lovar(n); - n = MIN(rpeasants(u->region), n); - if(n > 0) { - n = scareaway(u->region, n); - if(n > 0) { + if(n > 0) { + n = lovar(n); + n = MIN(rpeasants(u->region), n); + if(n > 0) { + n = scareaway(u->region, n); + if(n > 0) { ADDMSG(&u->region->msgs, msg_message("fleescared", "amount unit", n, u)); - } - } - } -} - -static const char * -random_growl(void) -{ - switch(rng_int()%5) { - case 0: - return "Groammm"; - case 1: - return "Roaaarrrr"; - case 2: - return "Chhhhhhhhhh"; - case 3: - return "Tschrrrkk"; - case 4: - return "Schhhh"; - } - return ""; -} - -extern struct attrib_type at_direction; - -static order * -monster_learn(unit *u) -{ - int c = 0; - int n; - skill * sv; - const struct locale * lang = u->faction->locale; - - /* Monster lernt ein zufälliges Talent aus allen, in denen es schon - * Lerntage hat. */ - - for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { - if (sv->level>0) ++c; - } - - if(c == 0) return NULL; - - n = rng_int()%c + 1; - c = 0; - - for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { - if (sv->level>0) { - if (++c == n) { - return create_order(K_STUDY, lang, "'%s'", skillname(sv->id, lang)); } } } - return NULL; } void -monsters_kill_peasants(unit * u) +monster_kills_peasants(unit * u) { - if (!is_waiting(u)) { + if (!monster_is_waiting(u)) { if (u->race->flags & RCF_SCAREPEASANTS) { scared_by_monster(u); } @@ -791,406 +228,3 @@ monsters_kill_peasants(unit * u) } } } - -static boolean -check_overpopulated(unit *u) -{ - unit *u2; - int c = 0; - - for(u2 = u->region->units; u2; u2 = u2->next) { - if(u2->race == u->race && u != u2) c += u2->number; - } - - if(c > u->race->splitsize * 2) return true; - - return false; -} - -static void -recruit_dracoids(unit * dragon, int size) -{ - faction * f = dragon->faction; - region * r = dragon->region; - const struct item * weapon = NULL; - order * new_order = NULL; - unit *un = createunit(r, f, size, new_race[RC_DRACOID]); - - fset(un, UFL_ISNEW|UFL_MOVED); - - name_unit(un); - change_money(dragon, -un->number * 50); - equip_unit(un, get_equipment("recruited_dracoid")); - - setstatus(un, ST_FIGHT); - for (weapon=un->items;weapon;weapon=weapon->next) { - const weapon_type * wtype = weapon->type->rtype->wtype; - if (wtype && (wtype->flags & WTF_MISSILE)) { - setstatus(un, ST_BEHIND); - } - new_order = create_order(K_STUDY, f->locale, "'%s'", - skillname(weapon->type->rtype->wtype->skill, f->locale)); - } - - if (new_order!=NULL) { - addlist(&un->orders, new_order); - } -} - -static order * -plan_dragon(unit * u) -{ - attrib * ta = a_find(u->attribs, &at_targetregion); - region * r = u->region; - region * tr = NULL; - boolean move = false; - order * long_order = NULL; - - reduce_weight(u); - - if (ta==NULL) { - move |= (r->land==0 || r->land->peasants==0); /* when no peasants, move */ - move |= (r->land==0 || r->land->money==0); /* when no money, move */ - } - move |= chance(0.04); /* 4% chance to change your mind */ - - if (u->race==new_race[RC_WYRM] && !move) { - unit * u2; - for (u2=r->units;u2;u2=u2->next) { - /* wyrme sind einzelgänger */ - if (u2==u) { - /* we do not make room for newcomers, so we don't need to look at them */ - break; - } - if (u2!=u && u2->race==u->race && chance(0.5)) { - move = true; - break; - } - } - } - - if (move) { - /* dragon gets bored and looks for a different place to go */ - ta = set_new_dragon_target(u, u->region, DRAGON_RANGE); - } - else ta = a_find(u->attribs, &at_targetregion); - if (ta!=NULL) { - tr = (region *) ta->data.v; - if (tr==NULL || !path_exists(u->region, tr, DRAGON_RANGE, allowed_dragon)) { - ta = set_new_dragon_target(u, u->region, DRAGON_RANGE); - if (ta) tr = findregion(ta->data.sa[0], ta->data.sa[1]); - } - } - if (tr!=NULL) { - assert(long_order==NULL); - switch(old_race(u->race)) { - case RC_FIREDRAGON: - long_order = make_movement_order(u, tr, 4, allowed_dragon); - break; - case RC_DRAGON: - long_order = make_movement_order(u, tr, 3, allowed_dragon); - break; - case RC_WYRM: - long_order = make_movement_order(u, tr, 1, allowed_dragon); - break; - } - if (rng_int()%100 < 15) { - const struct locale * lang = u->faction->locale; - /* do a growl */ - if (rname(tr, lang)) { - addlist(&u->orders, create_order(K_MAIL, lang, "%s '%s... %s %s %s'", - LOC(lang, parameters[P_REGION]), random_growl(), - u->number==1?"Ich rieche":"Wir riechen", - "etwas in", rname(tr, u->faction->locale))); - } - } - } else { - /* we have no target. do we like it here, then? */ - long_order = get_money_for_dragon(u->region, u, income(u)); - if (long_order==NULL) { - /* money is gone, need a new target */ - set_new_dragon_target(u, u->region, DRAGON_RANGE); - } - else if (u->race != new_race[RC_FIREDRAGON]) { - /* neue dracoiden! */ - if (r->land && !fval(r->terrain, FORBIDDEN_REGION)) { - int ra = 20 + rng_int() % 100; - if (get_money(u) > ra * 50 + 100 && rng_int() % 100 < 50) { - recruit_dracoids(u, ra); - } - } - } - } - if (long_order==NULL) { - skill_t sk = SK_PERCEPTION; - /* study perception (or a random useful skill) */ - while (!skill_enabled[sk] || u->race->bonus[sk]<-5) { - sk = (skill_t)(rng_int() % MAXSKILLS); - } - long_order = create_order(K_STUDY, u->faction->locale, "'%s'", - skillname(sk, u->faction->locale)); - } - return long_order; -} - -void -plan_monsters(void) -{ - region *r; - faction *f = get_monsters(); - - if (!f) return; - f->lastorders = turn; - - for (r = regions; r; r = r->next) { - unit *u; - double attack_chance = MONSTERATTACK; - boolean attacking = false; - - for (u = r->units; u; u = u->next) { - attrib * ta; - order * long_order = NULL; - - /* Ab hier nur noch Befehle für NPC-Einheiten. */ - if (!is_monsters(u->faction)) continue; - - if (attack_chance>0.0) { - if (chance(attack_chance)) attacking = true; - attack_chance = 0.0; - } - - if (u->status>ST_BEHIND) { - setstatus(u, ST_FIGHT); - /* all monsters fight */ - } - if (skill_enabled[SK_PERCEPTION]) { - /* Monster bekommen jede Runde ein paar Tage Wahrnehmung dazu */ - /* TODO: this only works for playerrace */ - produceexp(u, SK_PERCEPTION, u->number); - } - - /* Befehle müssen jede Runde neu gegeben werden: */ - free_orders(&u->orders); - - if (attacking) { - monster_attacks(u); - } - /* units with a plan to kill get ATTACK orders: */ - ta = a_find(u->attribs, &at_hate); - if (ta && !is_waiting(u)) { - unit * tu = (unit *)ta->data.v; - if (tu && tu->region==r) { - addlist(&u->orders, create_order(K_ATTACK, u->faction->locale, "%i", tu->no)); - } else if (tu) { - tu = findunitg(ta->data.i, NULL); - if (tu!=NULL) { - long_order = make_movement_order(u, tu->region, 2, allowed_walk); - } - } - else a_remove(&u->attribs, ta); - } - - /* All monsters guard the region: */ - if (!is_waiting(u) && r->land) { - addlist(&u->orders, create_order(K_GUARD, u->faction->locale, NULL)); - } - - /* Einheiten mit Bewegungsplan kriegen ein NACH: */ - if (long_order==NULL) { - attrib * ta = a_find(u->attribs, &at_targetregion); - if (ta) { - if (u->region == (region*)ta->data.v) { - a_remove(&u->attribs, ta); - } - } else if (u->race->flags & RCF_MOVERANDOM) { - if (rng_int()%100race->bonus[SK_WEAPONLESS] != -99) { - if (eff_skill(u, SK_WEAPONLESS, u->region) < 1) { - long_order = create_order(K_STUDY, f->locale, "'%s'", skillname(SK_WEAPONLESS, f->locale)); - } - } - } - - if (long_order==NULL) { - /* Ab hier noch nicht generalisierte Spezialbehandlungen. */ - - if (!u->orders) { - handle_event(u->attribs, "ai_move", u); - } - - switch (old_race(u->race)) { - case RC_SEASERPENT: - long_order = create_order(K_PIRACY, f->locale, NULL); - break; -#ifdef TODO_ALP - case RC_ALP: - long_order = monster_seeks_target(r, u); - break; -#endif - case RC_FIREDRAGON: - case RC_DRAGON: - case RC_WYRM: - long_order = plan_dragon(u); - break; - default: - if (u->race->flags & RCF_LEARN) { - long_order = monster_learn(u); - } - break; - } - } - if (long_order) { - addlist(&u->orders, long_order); - } - } - } - pathfinder_cleanup(); -} - -static double -chaosfactor(region * r) -{ - attrib * a = a_find(r->attribs, &at_chaoscount); - if (!a) return 0; - return ((double) a->data.i / 1000.0); -} - -static int -nrand(int start, int sub) -{ - int res = 0; - - do { - if (rng_int() % 100 < start) - res++; - start -= sub; - } while (start > 0); - - return res; -} - -/** Drachen und Seeschlangen können entstehen */ -void -spawn_dragons(void) -{ - region * r; - faction * monsters = get_monsters(); - - for (r = regions; r; r = r->next) { - unit * u; - - if (fval(r->terrain, SEA_REGION) && rng_int()%10000 < 1) { - u = createunit(r, monsters, 1, new_race[RC_SEASERPENT]); - fset(u, UFL_ISNEW|UFL_MOVED); - equip_unit(u, get_equipment("monster_seaserpent")); - } - - if ((r->terrain == newterrain(T_GLACIER) || r->terrain == newterrain(T_SWAMP) || r->terrain == newterrain(T_DESERT)) && rng_int() % 10000 < (5 + 100 * chaosfactor(r))) - { - if (chance(0.80)) { - u = createunit(r, monsters, nrand(60, 20) + 1, new_race[RC_FIREDRAGON]); - } else { - u = createunit(r, monsters, nrand(30, 20) + 1, new_race[RC_DRAGON]); - } - fset(u, UFL_ISNEW|UFL_MOVED); - equip_unit(u, get_equipment("monster_dragon")); - - if (verbosity>=2) { - log_printf("%d %s in %s.\n", u->number, - LOC(default_locale, rc_name(u->race, u->number!=1)), regionname(r, NULL)); - } - - name_unit(u); - - /* add message to the region */ - ADDMSG(&r->msgs, - msg_message("sighting", "region race number", r, u->race, u->number)); - } - } -} - -/** Untote können entstehen */ -void -spawn_undead(void) -{ - region * r; - faction * monsters = get_monsters(); - - for (r = regions; r; r = r->next) { - int unburied = deathcount(r); - static const curse_type * ctype = NULL; - - if (!ctype) ctype = ct_find("holyground"); - if (ctype && curse_active(get_curse(r->attribs, ctype))) continue; - - /* Chance 0.1% * chaosfactor */ - if (r->land && unburied > r->land->peasants / 20 && rng_int() % 10000 < (100 + 100 * chaosfactor(r))) { - unit * u; - /* es ist sinnfrei, wenn irgendwo im Wald 3er-Einheiten Untote entstehen. - * Lieber sammeln lassen, bis sie mindestens 5% der Bevölkerung sind, und - * dann erst auferstehen. */ - int undead = unburied / (rng_int() % 2 + 1); - const race * rc = NULL; - int i; - if (r->age<100) undead = undead * r->age / 100; /* newbie-regionen kriegen weniger ab */ - - if (!undead || r->age < 20) continue; - - switch(rng_int()%3) { - case 0: - rc = new_race[RC_SKELETON]; break; - case 1: - rc = new_race[RC_ZOMBIE]; break; - default: - rc = new_race[RC_GHOUL]; break; - } - - u = createunit(r, monsters, undead, rc); - fset(u, UFL_ISNEW|UFL_MOVED); - if ((rc == new_race[RC_SKELETON] || rc == new_race[RC_ZOMBIE]) && rng_int()%10 < 4) { - equip_unit(u, get_equipment("rising_undead")); - } - - for (i=0;i < MAXSKILLS;i++) { - if (rc->bonus[i] >= 1) { - set_level(u, (skill_t)i, 1); - } - } - u->hp = unit_max_hp(u) * u->number; - - deathcounts(r, -undead); - name_unit(u); - - if (verbosity>=2) { - log_printf("%d %s in %s.\n", u->number, - LOC(default_locale, rc_name(u->race, u->number!=1)), regionname(r, NULL)); - } - - { - message * msg = msg_message("undeadrise", "region", r); - add_message(&r->msgs, msg); - for (u=r->units;u;u=u->next) freset(u->faction, FFL_SELECT); - for (u=r->units;u;u=u->next) { - if (fval(u->faction, FFL_SELECT)) continue; - fset(u->faction, FFL_SELECT); - add_message(&u->faction->msgs, msg); - } - msg_release(msg); - } - } else { - int i = deathcount(r); - if (i) { - /* Gräber verwittern, 3% der Untoten finden die ewige Ruhe */ - deathcounts(r, (int)(-i*0.03)); - } - } - } -} diff --git a/src/src/gamecode/monster.h b/src/src/gamecode/monster.h index 0a0e7fdfa..499072ecc 100644 --- a/src/src/gamecode/monster.h +++ b/src/src/gamecode/monster.h @@ -25,16 +25,8 @@ extern "C" { #endif -#define DRAGON_RANGE 20 /* Max. Distanz zum nächsten Drachenziel */ - -void age_illusion(struct unit *u); - -void monsters_kill_peasants(struct unit * u); -void plan_monsters(void); -struct unit *random_unit(const struct region * r); - -extern void spawn_undead(void); -extern void spawn_dragons(void); +void monster_kills_peasants(struct unit * u); +boolean monster_is_waiting(const struct unit * u); #ifdef __cplusplus } diff --git a/src/src/gamecode/randenc.c b/src/src/gamecode/randenc.c index e54cbfe06..4ba77562f 100644 --- a/src/src/gamecode/randenc.c +++ b/src/src/gamecode/randenc.c @@ -425,6 +425,37 @@ chaosterrain(void) return types[rng_int() % numtypes]; } + +static unit * +random_unit(const region * r) +{ + int c = 0; + int n; + unit *u; + + for (u = r->units; u; u = u->next) { + if (u->race != new_race[RC_SPELL]) { + c += u->number; + } + } + + if (c == 0) { + return NULL; + } + n = rng_int() % c; + c = 0; + u = r->units; + + while (u && c < n) { + if (u->race != new_race[RC_SPELL]) { + c += u->number; + } + u = u->next; + } + + return u; +} + void chaos(region * r) { @@ -978,38 +1009,6 @@ godcurse(void) } -static unit * -split_unit(region * r, unit *u) -{ - unit *u2 = create_unit(r, u->faction, 0, u->race, 0, u->name, u); - int newsize = u->number/2; - - if (u->display) { - free(u2->display); - u2->display = strdup(u->display); - } - transfermen(u, u2, newsize); - return u2; -} - -static void -check_split(void) -{ - region *r; - unit *u; - - for(r=regions;r;r=r->next) { - for(u=r->units;u;u=u->next) { - if(is_monsters(u->faction)) { - if(u->number > u->race->splitsize) { - unit * u2 = split_unit(r, u); - fset(u2, UFL_ISNEW|UFL_MOVED); - } - } - } - } -} - /** handles the "orcish" curse that makes units grow like old orks * This would probably be better handled in an age-function for the curse, * but it's now being called by randomevents() @@ -1273,5 +1272,4 @@ randomevents(void) #endif dissolve_units(); - check_split(); } diff --git a/src/src/kernel/config.c b/src/src/kernel/config.c index 26a41107f..55a052398 100644 --- a/src/src/kernel/config.c +++ b/src/src/kernel/config.c @@ -92,9 +92,6 @@ #include #endif -/* exported variables */ -region *regions; -faction *factions; struct settings global = { "Eressea", /* gamename */ }; diff --git a/src/src/kernel/config.h b/src/src/kernel/config.h index afbc25abd..b4fb4adb5 100644 --- a/src/src/kernel/config.h +++ b/src/src/kernel/config.h @@ -166,9 +166,6 @@ extern int findoption(const char *s, const struct locale * lang); /* special units */ void make_undead_unit(struct unit *); -extern struct region *regions; -extern struct faction *factions; - void addstrlist(strlist ** SP, const char *s); int armedmen(const struct unit * u, boolean siege_weapons); diff --git a/src/src/kernel/faction.c b/src/src/kernel/faction.c index d2c62f54b..561de5b6b 100644 --- a/src/src/kernel/faction.c +++ b/src/src/kernel/faction.c @@ -49,6 +49,8 @@ #include #include +faction *factions; + /** remove the faction from memory. * this frees all memory that's only accessible through the faction, * but you should still call funhash and remove the faction from the diff --git a/src/src/kernel/faction.h b/src/src/kernel/faction.h index b76d19392..7abf959dc 100644 --- a/src/src/kernel/faction.h +++ b/src/src/kernel/faction.h @@ -103,6 +103,8 @@ typedef struct faction { struct seen_region ** seen; } faction; +extern struct faction *factions; + typedef struct faction_list { struct faction_list * next; struct faction * data; diff --git a/src/src/kernel/region.c b/src/src/kernel/region.c index 560ffb729..be17fef9b 100644 --- a/src/src/kernel/region.c +++ b/src/src/kernel/region.c @@ -64,6 +64,8 @@ extern int dice_rand(const char *s); +region *regions; + int get_maxluxuries() { diff --git a/src/src/kernel/region.h b/src/src/kernel/region.h index c2bf39f7b..ca7d5da8d 100644 --- a/src/src/kernel/region.h +++ b/src/src/kernel/region.h @@ -137,6 +137,8 @@ typedef struct region { #endif } region; +extern struct region *regions; + typedef struct region_list { struct region_list * next; struct region * data; diff --git a/src/src/races/dragons.c b/src/src/races/dragons.c index 795967c47..2dec1a359 100644 --- a/src/src/races/dragons.c +++ b/src/src/races/dragons.c @@ -31,23 +31,23 @@ void age_firedragon(unit *u) { - if (u->number>0 && rng_int()%100 < age_chance(u->age, DRAGONAGE, 1)) { - double q = (double) u->hp / (double) (unit_max_hp(u) * u->number); - u->race = new_race[RC_DRAGON]; - u->irace = NULL; - scale_number(u,1); - u->hp = (int) (unit_max_hp(u) * u->number * q); - } + if (u->number>0 && rng_int()%100 < age_chance(u->age, DRAGONAGE, 1)) { + double q = (double) u->hp / (double) (unit_max_hp(u) * u->number); + u->race = new_race[RC_DRAGON]; + u->irace = NULL; + scale_number(u,1); + u->hp = (int) (unit_max_hp(u) * u->number * q); + } } void age_dragon(unit *u) { - if (u->number>0 && rng_int()%100 < age_chance(u->age, WYRMAGE, 1)) { - double q = (double) u->hp / (double) (unit_max_hp(u) * u->number); - u->race = new_race[RC_WYRM]; - u->irace = NULL; - u->hp = (int) (unit_max_hp(u) * u->number * q); - } + if (u->number>0 && rng_int()%100 < age_chance(u->age, WYRMAGE, 1)) { + double q = (double) u->hp / (double) (unit_max_hp(u) * u->number); + u->race = new_race[RC_WYRM]; + u->irace = NULL; + u->hp = (int) (unit_max_hp(u) * u->number * q); + } } diff --git a/src/src/races/illusion.c b/src/src/races/illusion.c index f1c9a9d4a..cb5d3546e 100644 --- a/src/src/races/illusion.c +++ b/src/src/races/illusion.c @@ -30,13 +30,13 @@ void age_illusion(unit *u) { - if (u->faction->race!=new_race[RC_ILLUSION]) { - if (u->age == ILLUSIONMAX) { - ADDMSG(&u->faction->msgs, msg_message("warnillusiondissolve", + if (u->faction->race!=new_race[RC_ILLUSION]) { + if (u->age == ILLUSIONMAX) { + ADDMSG(&u->faction->msgs, msg_message("warnillusiondissolve", "unit", u)); - } else if (u->age > ILLUSIONMAX) { - set_number(u, 0); - ADDMSG(&u->faction->msgs, msg_message("illusiondissolve", "unit", u)); - } - } + } else if (u->age > ILLUSIONMAX) { + set_number(u, 0); + ADDMSG(&u->faction->msgs, msg_message("illusiondissolve", "unit", u)); + } + } }