server/src/common/gamecode/randenc.c
Enno Rehling 1aa45368a5 Skillsystem, 3. Version
Das linkt momentan wegen zwei fehlenden Funktionen noch nicht, die ich morgen nachreiche. Alte Varainte kompoiliert und linkt aber, also einfach nicht an den defines spielen im Moment.
2002-02-15 16:13:30 +00:00

1644 lines
39 KiB
C

/* vi: set ts=2:
*
*
* Eressea PB(E)M host Copyright (C) 1998-2000
* Christian Schlittchen (corwin@amber.kn-bremen.de)
* Katja Zedel (katze@felidae.kn-bremen.de)
* Henning Peters (faroul@beyond.kn-bremen.de)
* Enno Rehling (enno@eressea-pbem.de)
* Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
*
* based on:
*
* Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace
* Atlantis v1.7 Copyright 1996 by Alex Schröder
*
* This program may not be used, modified or distributed without
* prior permission by the authors of Eressea.
* This program may not be sold or used commercially without prior written
* permission from the authors.
*/
#include <config.h>
#include "eressea.h"
#include "randenc.h"
#include "unit.h"
#include "faction.h"
#include "alchemy.h"
#include "item.h"
#include "plane.h"
#include "economy.h"
#include "building.h"
#include "magic.h"
#include "message.h"
#include "race.h"
#include "monster.h"
#include "creation.h"
#include "names.h"
#include "pool.h"
#include "movement.h"
#include "curse.h"
#include "region.h"
#include "skill.h"
#include "karma.h"
#include "ship.h"
#include "battle.h"
#include "luck.h"
/* attributes includes */
#include <attributes/racename.h>
/* util includes */
#include <rand.h>
#include <util/message.h>
/* libc includes */
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <attributes/iceberg.h>
extern attrib_type at_unitdissolve;
extern attrib_type at_orcification;
int
nrand(int start, int sub)
{
int res = 0;
do {
if (rand() % 100 < start)
res++;
start -= sub;
} while (start > 0);
return res;
}
/* In a->data.ca[1] steht der Prozentsatz mit dem sich die Einheit
* auflöst, in a->data.ca[0] kann angegeben werden, wohin die Personen
* verschwinden. Passiert bereits in der ersten Runde! */
static void
dissolve_units(void)
{
region *r;
unit *u;
int n;
int i;
for (r=regions;r;r=r->next) {
for (u=r->units;u;u=u->next) {
attrib * a = a_find(u->attribs, &at_unitdissolve);
if (a) {
if (u->age == 0 && a->data.ca[1] < 100) continue;
/* TODO: Durch einzelne Berechnung ersetzen */
if (a->data.ca[1] == 100) {
n = u->number;
} else {
n = 0;
for (i=0;i<u->number;i++) {
if (rand()%100 < a->data.ca[1]) n++;
}
}
/* wenn keiner verschwindet, auch keine Meldung */
if (n == 0) {
continue;
}
scale_number(u,u->number - n);
sprintf(buf, "%s in %s: %d %s ", unitname(u), regionid(r),
n, LOC(default_locale, rc_name(u->race, n!=1)));
switch(a->data.ca[0]) {
case 1:
rsetpeasants(r, rpeasants(r) + n);
if (n == 1) {
scat("kehrte auf sein Feld zurück.");
}else{
scat("kehrten auf ihre Felder zurück.");
}
break;
case 2:
if(r->land) {
#if GROWING_TREES
rsettrees(r, 2, rtrees(r,2) + n);
#else
rsettrees(r, rtrees(r) + n);
#endif
if (n == 1) {
scat("wurde zum Baum.");
}else{
scat("wurden zu Bäumen.");
}
} else {
if(n == 1) {
scat("verfaulte in der feuchten Seeluft.");
} else {
scat("verfaulten in der feuchten Seeluft.");
}
}
break;
default:
if (u->race == new_race[RC_STONEGOLEM] || u->race == new_race[RC_IRONGOLEM]) {
if (n == 1) {
scat("zerfiel zu Staub.");
}else{
scat("zerfielen zu Staub.");
}
}else{
if (n == 1) {
scat("verschwand über Nacht.");
}else{
scat("verschwanden über Nacht.");
}
}
break;
}
addmessage(r, u->faction, buf, MSG_EVENT, ML_INFO);
}
}
}
remove_empty_units();
}
static int
improve_all(faction * f, skill_t sk, int weeks)
{
region *r;
unit *u;
int n = 0;
region *last = lastregion(f);
for (r = firstregion(f); r != last; r = r->next) {
for (u = r->units; u; u = u->next) {
if (u->faction == f && get_skill(u, sk)) {
#if SKILLPOINTS
change_skill(u, sk, u->number * weeks * 30);
n += u->number;
#else
for (n=0;n!=weeks;++n) {
learn_skill(u, sk, 1.0);
}
#endif
}
}
}
return n;
}
void
find_manual(region * r, unit * u)
{
skill_t skill = NOSKILL;
sprintf(buf, "%s stolper%c bei der Erforschung der Region über ",
unitname(u), "nt"[u->number == 1]);
switch (rand() % 4) {
case 0:
scat("die Ruine eines alten Tempels");
break;
case 1:
scat("eine alte Burgruine");
break;
case 2:
scat("ein zerfallenes Bauernhaus");
break;
case 3:
scat("eine Leiche am Wegesrand");
break;
}
scat(". Bei der Durchsuchung ");
if (u->number == 1) {
scat("stößt");
} else {
scat("stoßen");
}
scat(" sie auf das zerfledderte Exemplar eines alten Buches, betitelt ");
switch (rand() % 36) {
case 0:
scat("\'Magie der Elemente\'");
skill = SK_MAGIC;
break;
case 1:
case 2:
case 3:
case 4:
scat("\'Schwerter, Armbrüste, Langbögen\'");
skill = SK_WEAPONSMITH;
break;
case 5:
case 6:
scat("\'Gorms Almanach der Rationellen Kriegsführung\'");
skill = SK_TACTICS;
break;
case 7:
case 8:
case 9:
case 10:
scat("\'Katamarane, Koggen, Karavellen\'");
skill = SK_SHIPBUILDING;
break;
case 11:
case 12:
case 13:
case 14:
scat("\'Wege der Sterne\'");
skill = SK_SAILING;
break;
case 15:
case 16:
case 17:
scat("\'Nadishahs Kleine Gift- und Kräuterkunde\'");
skill = SK_HERBALISM;
break;
case 18:
case 19:
scat("\'Mandricks Kompendium der Alchemie\'");
skill = SK_ALCHEMY;
break;
case 20:
case 21:
case 22:
case 23:
scat("\'Die Konstruktion der Burgen und Schlösser von Zentralandune\'");
skill = SK_BUILDING;
break;
case 24:
case 25:
case 26:
case 27:
scat("\'Die Esse\'");
skill = SK_ARMORER;
break;
case 28:
case 29:
case 30:
case 31:
scat("\'Über die Gewinnung von Erzen\'");
skill = SK_MINING;
break;
case 32:
case 33:
case 34:
case 35:
scat("\'Barinions Lieder, eine Einführung für Unbedarfte\'");
skill = SK_ENTERTAINMENT;
break;
}
scat(". Der Wissensschub ist enorm.");
addmessage(r, u->faction, buf, MSG_EVENT, ML_IMPORTANT);
if (improve_all(u->faction, skill, 3) == 0) {
#if SKILLPOINTS
change_skill(u, skill, 270);
#else
int i;
for (i=0;i!=9;++i) learn_skill(u, skill, 1.0);
#endif
}
}
static void
get_unit(region * r, unit * u)
{
unit *newunit;
sprintf(buf, "%s entdeck%s ein kleines Dorf. Die meisten Häuser "
"wurden durch einen über die Ufer getretenen Fluß zerstört. Eine "
"Gruppe der verzweifelten Menschen schließt sich deiner Partei an.",
unitname(u), "en\0t" + (3 - 3 * (u->number == 1)));
addmessage(r, u->faction, buf, MSG_EVENT, ML_IMPORTANT);
newunit = createunit(r, u->faction, rand() % 20 + 3, u->faction->race);
set_string(&newunit->name, "Dorfbewohner");
set_money(newunit, (rand() % 26 + 10) * newunit->number);
fset(newunit, FL_ISNEW);
if (fval(u, FL_PARTEITARNUNG)) fset(newunit, FL_PARTEITARNUNG);
switch (rand() % 4) {
case 0:
set_level(newunit, SK_MINING, 1);
break;
case 1:
set_level(newunit, SK_LUMBERJACK, 1);
break;
case 2:
set_level(newunit, SK_CARTMAKER, 1);
break;
case 3:
set_level(newunit, SK_QUARRYING, 1);
break;
}
set_item(newunit, I_WAGON, rand() % 2);
set_item(newunit, I_HORSE, min(get_item(newunit, I_WAGON) * 2, rand() % 5));
}
static void
get_allies(region * r, unit * u)
{
unit *newunit = NULL;
switch (rterrain(r)) {
case T_PLAIN:
if (!r_isforest(r)) {
if (get_money(u) / u->number < 100 + rand() % 200)
return;
newunit = createunit(r, u->faction, rand() % 8 + 2, u->faction->race);
set_string(&newunit->name, "Söldner");
set_money(newunit, (rand() % 80 + 20) * newunit->number);
switch (rand() % 4) {
case 0:
set_level(newunit, SK_SWORD, 1+rand()%3);
set_item(newunit, I_SWORD, newunit->number);
break;
case 1:
set_level(newunit, SK_SPEAR, 1+rand()%3);
set_item(newunit, I_SPEAR, newunit->number);
break;
case 2:
set_level(newunit, SK_CROSSBOW, 1+rand()%3);
set_item(newunit, I_CROSSBOW, newunit->number);
break;
case 3:
set_level(newunit, SK_LONGBOW, 1+rand()%3);
set_item(newunit, I_LONGBOW, newunit->number);
break;
}
if (rand() % 100 < 40) {
set_item(newunit, I_CHAIN_MAIL, rand() % (newunit->number + 1));
}
if (rand() % 100 < 30) {
set_item(newunit, I_HORSE, newunit->number);
set_level(newunit, SK_RIDING, 1+rand()%3);
}
break;
} else {
if (eff_skill(u, SK_LONGBOW, r) < 3
&& eff_skill(u, SK_HERBALISM, r) < 2
&& eff_skill(u, SK_MAGIC, r) < 2) {
return;
}
newunit = createunit(r, u->faction, rand() % 6 + 2, u->faction->race);
set_string(&newunit->name, "Waldbewohner");
set_money(newunit, (rand() % 20 + 10) * newunit->number);
set_level(newunit, SK_LONGBOW, 2+rand()%3);
set_item(newunit, I_LONGBOW, newunit->number);
set_level(newunit, SK_OBSERVATION, 2+rand()%2);
set_level(newunit, SK_STEALTH, 1+rand()%2);
if (rand() % 100 < 20) {
set_level(newunit, SK_HERBALISM, 1+rand()%2);
}
}
break;
case T_SWAMP:
if (eff_skill(u, SK_OBSERVATION, r) <= 3) {
return;
}
newunit = createunit(r, u->faction, rand() % 6 + 2, u->faction->race);
set_string(&newunit->name, "Sumpfbewohner");
set_money(newunit, (rand() % 20 + 10) * newunit->number);
set_level(newunit, SK_SPEAR, 2+rand()%3);
set_item(newunit, I_SPEAR, newunit->number);
set_level(newunit, SK_STEALTH, 2+rand()%3);
break;
case T_DESERT:
if (eff_skill(u, SK_RIDING, r) <= 2) {
return;
}
newunit = createunit(r, u->faction, rand() % 12 + 2, u->faction->race);
set_string(&newunit->name, "Berber");
set_money(newunit, (rand() % 30 + 20) * newunit->number);
set_level(newunit, SK_SWORD, 1+rand()%2);
set_item(newunit, I_SWORD, newunit->number);
set_level(newunit, SK_TRADE, 1+rand()%3);
set_level(newunit, SK_RIDING, 2+rand()%2);
set_item(newunit, I_HORSE, newunit->number);
set_level(newunit, SK_HORSE_TRAINING, 2+rand()%2);
break;
case T_HIGHLAND:
if (eff_skill(u, SK_SWORD, r) <= 1) {
return;
}
newunit = createunit(r, u->faction, rand() % 8 + 2, u->faction->race);
set_string(&newunit->name, "Hochlandbarbaren");
set_money(newunit, (rand() % 10 + 20) * newunit->number);
set_level(newunit, SK_SWORD, 1+rand()%2);
set_item(newunit, I_SWORD, newunit->number);
break;
case T_MOUNTAIN:
if (eff_skill(u, SK_SWORD, r) <= 1
|| eff_skill(u, SK_TRADE, r) <= 2) {
return;
}
newunit = createunit(r, u->faction, rand() % 6 + 2, u->faction->race);
set_string(&newunit->name, "Bergbewohner");
set_money(newunit, (rand() % 40 + 60) * newunit->number);
set_level(newunit, SK_SWORD, 2+rand()%2);
set_item(newunit, I_SWORD, newunit->number);
set_level(newunit, SK_ARMORER, 2+rand()%2);
set_level(newunit, SK_TRADE, 1+rand()%3);
if (rand() % 100 < 60) {
set_item(newunit, I_PLATE_ARMOR, newunit->number);
}
break;
case T_GLACIER:
if (eff_skill(u, SK_SWORD, r) <= 1
|| eff_skill(u, SK_TRADE, r) <= 1) {
return;
}
newunit = createunit(r, u->faction, rand() % 4 + 2, u->faction->race);
set_string(&newunit->name, "Eisleute");
set_money(newunit, (rand() % 20 + 20) * newunit->number);
set_level(newunit, SK_SWORD, 2+rand()%2);
set_item(newunit, I_SWORD, newunit->number);
set_level(newunit, SK_ARMORER, 2+rand()%2);
break;
}
u_setfaction(newunit, u->faction);
set_racename(&newunit->attribs, get_racename(u->attribs));
if(u->race->flags & RCF_SHAPESHIFT) {
newunit->irace = u->irace;
}
if (fval(u, FL_PARTEITARNUNG)) fset(newunit, FL_PARTEITARNUNG);
fset(u, FL_ISNEW);
sprintf(buf, "Plötzlich stolper%c %s über einige %s. Nach kurzem "
"Zögern entschließen sich die %s, sich Deiner Partei anzuschließen.",
u->number == 1 ? 't' : 'n', unitname(u), newunit->name, newunit->name);
addmessage(r, u->faction, buf, MSG_EVENT, ML_IMPORTANT);
}
static void
encounter(region * r, unit * u)
{
if (!fval(r, RF_ENCOUNTER)) return;
freset(r, RF_ENCOUNTER);
if (rand() % 100>=ENCCHANCE) return;
switch (rand() % 3) {
case 0:
find_manual(r, u);
break;
case 1:
get_unit(r, u);
break;
case 2:
get_allies(r, u);
break;
}
}
void
encounters(void)
{
region *r;
unit *u;
int n;
int c;
int i;
for (r = regions; r; r = r->next) {
if (rterrain(r) != T_OCEAN && fval(r, RF_ENCOUNTER)) {
c = 0;
for (u = r->units; u; u = u->next) {
c += u->number;
}
if (c > 0) {
n = rand() % c;
u = r->units;
for (i = u->number; i < n; i += u->number) {
u = u->next;
}
encounter(r, u);
}
}
}
}
void
chaos(region * r)
{
unit *u = NULL, *u2;
building *b, *b2;
if (rand() % 100 < 8) {
switch (rand() % 3) {
case 0: /* Untote */
if (rterrain(r) != T_OCEAN) {
u = random_unit(r);
if (u && playerrace(u->race)) {
sprintf(buf, "%s scheint von einer seltsamen Krankheit befallen.",
unitname(u));
addmessage(0, u->faction, buf, MSG_EVENT, ML_IMPORTANT);
u_setfaction(u, findfaction(MONSTER_FACTION));
u->race = new_race[RC_GHOUL];
}
}
break;
case 1: /* Drachen */
if (random_unit(r)) {
int mfac = 0;
switch (rand() % 3) {
case 0:
mfac = 100;
u = createunit(r, findfaction(MONSTER_FACTION), rand() % 8 + 1, new_race[RC_FIREDRAGON]);
if (u->number == 1) {
set_string(&u->name, "Feuerdrache");
} else {
set_string(&u->name, "Feuerdrachen");
}
break;
case 1:
mfac = 500;
u = createunit(r, findfaction(MONSTER_FACTION), rand() % 4 + 1, new_race[RC_DRAGON]);
if (u->number == 1) {
set_string(&u->name, "Drache");
} else {
set_string(&u->name, "Drachen");
}
break;
case 2:
mfac = 1000;
u = createunit(r, findfaction(MONSTER_FACTION), rand() % 2 + 1, new_race[RC_WYRM]);
if (u->number == 1) {
set_string(&u->name, "Wyrm");
} else {
set_string(&u->name, "Wyrme");
}
break;
}
if (mfac) set_money(u, u->number * (rand() % mfac));
guard(u, GUARD_ALL);
}
case 2: /* Terrainveränderung */
if (!(terrain[rterrain(r)].flags & FORBIDDEN_LAND)) {
if (rterrain(r) != T_OCEAN) {
direction_t dir;
for (dir=0;dir!=MAXDIRECTIONS;++dir) {
if (rconnect(r, dir) && rterrain(rconnect(r, dir)) == T_OCEAN) break;
}
if (dir!=MAXDIRECTIONS) {
ship * sh = r->ships;
while (sh) {
ship * nsh = sh->next;
damage_ship(sh, 0.50);
if (sh->damage >= sh->size * DAMAGE_SCALE) destroy_ship(sh, r);
sh = nsh;
}
for (u = r->units; u;) {
u2 = u->next;
if (u->race != new_race[RC_SPELL] && u->ship == 0) {
set_number(u, 0);
}
u = u2;
}
sprintf(buf, "Ein gewaltige Flutwelle verschlingt %s und "
"alle Bewohner.", regionid(r));
addmessage(r, 0, buf, MSG_EVENT, ML_IMPORTANT);
for (b = rbuildings(r); b;) {
b2 = b->next;
destroy_building(b);
b = b2;
}
terraform(r, T_OCEAN);
}
} else {
direction_t dir;
for (dir=0;dir!=MAXDIRECTIONS;++dir) {
if (rconnect(r, dir) && rterrain(rconnect(r, dir)) != T_OCEAN) break;
}
if (dir!=MAXDIRECTIONS) {
switch (rand() % 8) {
case 0:
terraform(r, T_PLAIN);
break;
case 1:
terraform(r, T_HIGHLAND);
break;
case 2:
terraform(r, T_MOUNTAIN);
break;
case 3:
terraform(r, T_GLACIER);
break;
case 4:
terraform(r, T_DESERT);
break;
default:
terraform(r, T_SWAMP);
break;
}
}
}
}
}
}
}
double
chaosfactor(region * r)
{
attrib * a = a_find(r->attribs, &at_chaoscount);
if (!a) return 0;
return ((double) a->data.i / 1000.0);
}
void
drown(region *r)
{
if (rterrain(r) == T_OCEAN) {
unit ** up = up=&r->units;
while (*up) {
unit *u = *up;
int amphibian_level = fspecial(u->faction, FS_AMPHIBIAN);
if (u->ship || u->race == new_race[RC_SPELL]) {
up=&u->next;
continue;
}
if (amphibian_level) {
int dead = damage_unit(u, "5d1", false, false);
if (dead) {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"drown_amphibian_dead%d:amount%u:unit%r:region",dead, u, r));
} else {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"drown_amphibian_nodead%u:unit%r:region",u, r));
}
} else if (!canswim(u)) {
scale_number(u, 0);
ADDMSG(&u->faction->msgs, new_message(u->faction,
"drown%u:unit%r:region", u, r));
}
if (*up==u) up=&u->next;
}
remove_empty_units_in_region(r);
}
}
region *
rrandneighbour(region *r)
{
direction_t i;
region *rc = NULL;
int rr, c = 0;
/* Nachsehen, wieviele Regionen in Frage kommen */
for (i = 0; i != MAXDIRECTIONS; i++) {
c++;
}
/* Zufällig eine auswählen */
rr = rand() % c;
/* Durchzählen */
c = -1;
for (i = 0; i != MAXDIRECTIONS; i++) {
rc = rconnect(r, i);
c++;
if (c == rr) break;
}
assert(i!=MAXDIRECTIONS);
return rc;
}
void
volcano_outbreak(region *r)
{
attrib *a;
region *rn;
unit *u, **up;
int dead;
faction *f;
for (u=r->units; u; u=u->next) {
f = u->faction;
freset(f, FL_DH);
}
rn = rrandneighbour(r);
/* Vulkan-Region verwüsten */
#if GROWING_TREES
rsettrees(r, 2, 0);
rsettrees(r, 1, 0);
rsettrees(r, 0, 0);
#else
rsettrees(r, 0);
#endif
a = a_find(r->attribs, &at_reduceproduction);
if (!a) a = a_add(&r->attribs, a_new(&at_reduceproduction));
/* Produktion vierteln ... */
a->data.sa[0] = 25;
/* Für 6-17 Runden */
a->data.sa[1] = (short)(a->data.sa[1] + 6 + rand()%12);
/* Personen bekommen 4W10 Punkte Schaden. */
for (up=&r->units; *up;) {
unit * u = *up;
dead = damage_unit(u, "4d10", true, false);
if (dead) {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"volcano_dead%u:unit%r:region%i:dead", u, r, dead));
}
if (!fval(u->faction, FL_DH)) {
fset(u->faction, FL_DH);
if (rn) {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"volcanooutbreak%r:regionv%r:regionn", r, rn));
} else {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"volcanooutbreaknn%r:region", r));
}
}
if (u==*up) up=&u->next;
}
remove_empty_units_in_region(r);
/* Zufällige Nachbarregion verwüsten */
if (rn) {
#if GROWING_TREES
rsettrees(r, 2, 0);
rsettrees(r, 1, 0);
rsettrees(r, 0, 0);
#else
rsettrees(r, 0);
#endif
a = a_add(&rn->attribs, a_new(&at_reduceproduction));
if (!a) a = a_add(&r->attribs, a_new(&at_reduceproduction));
/* Produktion vierteln ... */
a->data.sa[0] = 25;
/* Für 6-17 Runden */
a->data.sa[1] = (short)(a->data.sa[1] + 6 + rand()%12);
/* Personen bekommen 3W10 Punkte Schaden. */
for (up=&rn->units; *up;) {
unit * u = *up;
dead = damage_unit(u, "3d10", true, false);
if (dead) {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"volcano_dead%u:unit%r:region%i:dead", u, rn, dead));
}
if (!fval(u->faction, FL_DH)) {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"volcano_dead%u:unit%r:region%i:dead", u, r, dead));
fset(u->faction, FL_DH);
}
if (u==*up) up=&u->next;
}
remove_empty_units_in_region(rn);
}
}
static void
melt_iceberg(region *r)
{
attrib *a;
unit *u;
building *b, *b2;
for (u=r->units; u; u=u->next) freset(u->faction, FL_DH);
for (u=r->units; u; u=u->next) if (!fval(u->faction, FL_DH)) {
fset(u->faction, FL_DH);
ADDMSG(&u->faction->msgs, new_message(u->faction,
"iceberg_melt%r:region", r));
}
/* driftrichtung löschen */
a = a_find(r->attribs, &at_iceberg);
if (a) a_remove(&r->attribs, a);
/* Gebäude löschen */
for (b = rbuildings(r); b; b = b2) {
b2 = b->next;
destroy_building(b);
}
/* in Ozean wandeln */
rsetterrain(r, T_OCEAN);
/* Einheiten, die nicht schwimmen können oder in Schiffen sind,
* ertrinken */
drown(r);
}
static void
move_iceberg(region *r)
{
attrib *a;
direction_t dir;
region *rc;
a = a_find(r->attribs, &at_iceberg);
if (!a) {
dir = (direction_t)(rand()%MAXDIRECTIONS);
a = a_add(&r->attribs, make_iceberg(dir));
} else {
if (rand()%100 < 20) {
dir = (direction_t)(rand()%MAXDIRECTIONS);
a->data.i = dir;
} else {
dir = (direction_t)a->data.i;
}
}
rc = rconnect(r, dir);
if (rc && rterrain(rc) != T_GLACIER && rterrain(rc) != T_ICEBERG
&& rterrain(rc) != T_ICEBERG_SLEEP) {
if (rterrain(rc) == T_OCEAN) { /* Eisberg treibt */
ship *sh, *shn;
unit *u;
int x, y;
for (u=r->units; u; u=u->next) freset(u->faction, FL_DH);
for (u=r->units; u; u=u->next) if (!fval(u->faction, FL_DH)) {
fset(u->faction, FL_DH);
ADDMSG(&u->faction->msgs, new_message(u->faction,
"iceberg_drift%r:region%d:dir", r, dir));
}
x = r->x;
y = r->y;
runhash(r);
runhash(rc);
r->x = rc->x;
r->y = rc->y;
rc->x = x;
rc->y = y;
rhash(rc);
rhash(r);
/* rc ist der Ozean (Ex-Eisberg), r der Eisberg (Ex-Ozean) */
/* Schiffe aus dem Zielozean werden in den Eisberg transferiert
* und nehmen Schaden. */
for (sh = r->ships; sh; sh=sh->next) freset(sh, FL_DH);
for (sh = r->ships; sh; sh = sh->next) {
/* Meldung an Kapitän */
damage_ship(sh, 0.10);
fset(sh, FL_DH);
}
/* Personen, Schiffe und Gebäude verschieben */
while (rc->buildings) {
translist(&rc->buildings, &r->buildings, rc->buildings);
}
while (rc->ships) {
fset(rc->ships, FL_DH);
damage_ship(rc->ships, 0.10);
move_ship(rc->ships, rc, r, NULL);
}
while (rc->units) {
building * b = rc->units->building;
u = rc->units;
move_unit(rc->units, r, NULL);
u->building = b; /* move_unit macht ein leave() */
}
/* Beschädigte Schiffe können sinken */
for (sh = r->ships; sh;) {
shn = sh->next;
if (fval(sh, FL_DH)) {
u = captain(sh, r);
if (sh->damage>=sh->size * DAMAGE_SCALE) {
if (u) ADDMSG(&u->faction->msgs, new_message(u->faction,
"overrun_by_iceberg_des%h:ship", sh));
destroy_ship(sh, r);
} else {
if (u) ADDMSG(&u->faction->msgs, new_message(u->faction,
"overrun_by_iceberg%h:ship", sh));
}
}
sh = shn;
}
} else if (rand()%100 < 20) { /* Eisberg bleibt als Gletscher liegen */
unit *u;
rsetterrain(r, T_GLACIER);
a_remove(&r->attribs, a);
for (u=r->units; u; u=u->next) freset(u->faction, FL_DH);
for (u=r->units; u; u=u->next) if (!fval(u->faction, FL_DH)) {
fset(u->faction, FL_DH);
ADDMSG(&u->faction->msgs, new_message(u->faction,
"iceberg_land%r:region", r));
}
}
}
}
void
move_icebergs(void)
{
region *r;
for (r=regions; r; r=r->next) if (rterrain(r) == T_ICEBERG && !fval(r, RF_DH)) {
if (rand()%100 < 60) {
fset(r, RF_DH);
move_iceberg(r);
} else if (rand()%100 < 10){
fset(r, RF_DH);
melt_iceberg(r);
}
}
}
void
create_icebergs(void)
{
region *r;
for (r=regions; r; r=r->next) if (rterrain(r) == T_ICEBERG_SLEEP && rand()%100 < 5) {
boolean has_ocean_neighbour = false;
direction_t dir;
region *rc;
unit *u;
for (dir=0; dir < MAXDIRECTIONS; dir++) {
rc = rconnect(r, dir);
if (rc && rterrain(rc) == T_OCEAN) {
has_ocean_neighbour = true;
break;
}
}
if (!has_ocean_neighbour) continue;
rsetterrain(r, T_ICEBERG);
fset(r, RF_DH);
move_iceberg(r);
for (u=r->units; u; u=u->next) freset(u->faction, FL_DH);
for (u=r->units; u; u=u->next) if (!fval(u->faction, FL_DH)) {
fset(u->faction, FL_DH);
ADDMSG(&u->faction->msgs, msg_message("iceberg_create", "region", r));
}
}
}
void
godcurse(void)
{
region *r;
ship *sh, *shn;
for(r=regions; r; r=r->next) {
if(is_cursed(r->attribs, C_CURSED_BY_THE_GODS, 0)) {
unit * u;
for(u=r->units; u; u=u->next) {
#if SKILLPOINTS
skill_t sk;
for(sk=0; sk < MAXSKILLS; sk++) {
int s = get_skill(u, sk);
if (s > 0) {
change_skill(u, sk, -min(s, (30+rand()%90)*u->number));
}
}
#else
skill * sv = u->skills;
while (sv!=u->skills+u->skill_size) {
int weeks = 1+rand()%3;
reduce_skill(sv, weeks);
++sv;
}
#endif
}
}
}
if(rterrain(r) == T_OCEAN) {
for (sh = r->ships; sh;) {
shn = sh->next;
damage_ship(sh, 0.10);
if (sh->damage>=sh->size * DAMAGE_SCALE) {
unit * u = shipowner(r, sh);
if (u) ADDMSG(&u->faction->msgs,
msg_message("godcurse_destroy_ship", "ship", sh));
destroy_ship(sh, r);
}
sh = shn;
}
}
}
void
randomevents(void)
{
region *r;
building *b, *b2;
unit *u;
int unfed;
/* Eiseberge */
for (r=regions; r; r=r->next) freset(r, RF_DH);
create_icebergs();
move_icebergs();
godcurse();
for (r=regions; r; r=r->next) {
drown(r);
}
#if RACE_ADJUSTMENTS == 0
/* Orks vermehren sich */
for (r = regions; r; r = r->next) {
plane * p = rplane(r);
/* there is a flag for planes without orc growth: */
if (p && (p->flags & PFL_NOORCGROWTH)) continue;
for (u = r->units; u; u = u->next) {
if ( (u->race == new_race[RC_ORC] || is_cursed(u->attribs, C_ORC, 0))
&& !get_skill(u, SK_MAGIC)
&& !get_skill(u, SK_ALCHEMY)) {
int increase = 0;
int num, prob;
if (u->race == new_race[RC_ORC]) {
num = u->number;
prob = 5;
} else {
num = min(get_cursedmen(u->attribs, C_ORC, 0), u->number);
prob = get_curseeffect(u->attribs, C_ORC, 0);
}
for (n = (num - get_item(u, I_CHASTITY_BELT)); n > 0; n--) {
if (rand() % 100 < prob) {
++increase;
}
}
if (increase) {
if (u->race == new_race[RC_ORC]) {
int i;
struct orcskills {
skill_t skill;
int level;
} skills [] = {
{ SK_SWORD, 1 }, { SK_SPEAR, 1 }, { SK_TACTICS, 0 },
{ SK_LONGBOW, 0 }, { SK_CROSSBOW, 0 }, { SK_CATAPULT, 0 },
{ SK_AUSDAUER, 0 }, { NOSKILL, 0 }
};
for (i=0;skills[i].skill!=NOSKILL;++i) {
int s = get_skill(u, skills[i].skill) / (u->number * 2);
int k = skill_level(skills[i].level);
change_skill(u, skills[i].skill, increase * max(k, s));
}
}
set_number(u, u->number + increase);
u->hp += unit_max_hp(u) * increase;
ADDMSG(&u->faction->msgs, msg_message("orcgrowth",
"unit amount race", u, increase, u->race));
}
}
}
}
#endif
/* Talente von Dämonen verschieben sich und Dämonen fressen Bauern */
for (r = regions; r; r = r->next) {
int bauernblut = 0;
boolean bfind = false;
for (u = r->units; u; u = u->next) {
if (u->race == new_race[RC_DAEMON]) {
/* Alles Bauernblut der Region zählen.
* warnung: bauernblut einer partei hilft im moment der anderen
* so selten wie das benutzt wird, ist das erstmal wursht,
* aber ein TODO fürs BUGS File.
* Es ist auch deshalb fast egal, weil es ja im Grunde nicht dem Dämon,
* sondern der Region zu gute kommt - und da ist der anwender schnuppe
*/
#if SKILLPOINTS
skill_t sk;
#else
skill * sv;
#endif
int dc;
if (!bfind) {
unit * ud = u;
while (ud) {
attrib * a = a_find(ud->attribs, &at_bauernblut);
if (a) bauernblut += a->data.i;
do { ud=ud->next; } while (ud && ud->race!=new_race[RC_DAEMON]);
}
bfind = true;
}
if (r->planep && !fval(r->planep, PFL_NOFEED)) {
unfed = (u->number - bauernblut) - rpeasants(r);
if (unfed > 0) {
#ifdef DAEMON_HUNGER
hunger(u, unfed); /* nicht gefütterte dämonen hungern */
#else
c = 0;
for (n = 0; n < unfed; n++) {
if (rand() % 100 < 10) {
c++;
}
}
if (c) {
scale_number(u, u->number - c);
sprintf(buf, "%d Dämonen von %s sind hungrig in "
"ihre Sphäre zurückgekehrt.", c, unitname(u));
addmessage(0, u->faction, buf, MSG_EVENT, ML_IMPORTANT);
}
#endif
}
}
dc = max(u->number - bauernblut, 0);
dc = min(dc, rpeasants(r));
dc = rpeasants(r) - max(u->number - bauernblut, 0);
dc = max(0, dc);
rsetpeasants(r, dc);
#if SKILLPOINTS
for (sk = 0; sk != MAXSKILLS; sk++) {
if (get_skill(u, sk) && rand() % 100 < 25) {
int change = rand() % 90 + 1;
if (rand() % 100 < 60) {
change_skill(u, sk, u->number * change);
} else {
int s = get_skill(u, sk);
s = min(s, u->number * change);
change_skill(u, sk, -s);
}
}
}
#else
sv = u->skills;
while (sv!=u->skills+u->skill_size) {
if (rand() % 100 < 25) {
int weeks = 0+rand()%3;
if (rand() % 100 < 40) reduce_skill(sv, weeks);
else while (weeks--) learn_skill(u, sv->id, 1.0);
}
++sv;
}
#endif
}
}
}
for (r = regions; r; r = r->next) {
#if !RACE_ADJUSTMENTS
/* Elfen generieren Wald */
if (r->land && !fval(r, RF_MALLORN)) {
#if GROWING_TREES
int trees = rtrees(r, 2);
#else
int trees = rtrees(r);
#endif
int maxgen = (production(r) * MAXPEASANTS_PER_AREA)/8;
for (u = r->units; u && maxgen > 0; u = u->next) {
if (u->race == new_race[RC_ELF]) {
for (n = u->number; n && maxgen > 0; n--) {
if (rand() % 1000 < 15) { /* 1.5% Chance */
trees++;
}
maxgen--;
}
}
}
#if GROWING_TREES
rsettrees(r, 2, trees);
#else
rsettrees(r, trees);
#endif /* GROWING_TREES */
} /* !RACE_ADJUSTMENTS */
#endif
for (u=r->units; u; u=u->next) {
if (!(u->race->ec_flags & NOGIVE)) {
struct building * b = inside_building(u);
const struct building_type * btype = b?b->type:NULL;
if (btype == &bt_blessedstonecircle) {
int n, c = 0;
for (n=0; n<u->number; n++) if (rand()%100 < 2) {
change_item(u, I_UNICORN, 1);
c++;
}
if (c) {
ADDMSG(&u->faction->msgs, new_message(u->faction,
"scunicorn%u:unit%i:amount%X:type",u,c,
olditemtype[I_UNICORN]->rtype));
}
}
}
}
}
/* Orkifizierte Regionen mutieren und mutieren zurück */
for (r = regions; r; r = r->next) {
if (fval(r, RF_ORCIFIED)) {
direction_t dir;
int chance = 0;
for (dir = 0; dir < MAXDIRECTIONS; dir++) {
region *rc = rconnect(r, dir);
if (rc && rpeasants(rc) > 0 && !fval(rc, RF_ORCIFIED)) chance += 2;
}
if (rand()%100 < chance) {
ADDMSG(&r->msgs, msg_message("deorcified", "region", r));
freset(r, RF_ORCIFIED);
}
} else {
attrib *a = a_find(r->attribs, &at_orcification);
if (a!=NULL) {
int chance = 0;
if (rpeasants(r) <= 0) continue;
chance = (a->data.i*100)/rpeasants(r);
if (rand()%100 < chance) {
fset(r, RF_ORCIFIED);
a_remove(&r->attribs, a);
ADDMSG(&r->msgs, msg_message("orcified", "region", r));
} else {
a->data.i -= max(10,a->data.i/10);
if (a->data.i <= 0) a_remove(&r->attribs, a);
}
}
}
}
/* Vulkane qualmen, brechen aus ... */
for (r = regions; r; r = r->next) {
switch(rterrain(r)) {
case T_VOLCANO:
if (rand()%100 < 4) {
ADDMSG(&r->msgs, msg_message("volcanostartsmoke", "region", r));
rsetterrain(r, T_VOLCANO_SMOKING);
}
break;
case T_VOLCANO_SMOKING:
if (rand()%100 < 12) {
ADDMSG(&r->msgs, msg_message("volcanostopsmoke", "region", r));
rsetterrain(r, T_VOLCANO);
} else if (rand()%100 < 8) {
volcano_outbreak(r);
}
break;
}
}
/* Monumente zerfallen, Schiffe verfaulen */
for (r = regions; r; r = r->next) {
for (b = rbuildings(r); b; b = b2) {
b2 = b->next;
if (fval(b->type, BTF_DECAY) && !buildingowner(r, b)) {
b->size -= max(1, (b->size * 20) / 100);
if (b->size == 0) {
destroy_building(b);
}
}
}
}
/* Drachen und Seeschlangen können entstehen */
printf("\n");
for (r = regions; r; r = r->next) {
unit * u;
if (rterrain(r) == T_OCEAN && rand()%10000 < 1) {
u = createunit(r, findfaction(MONSTER_FACTION), 1, new_race[RC_SEASERPENT]);
set_level(u, SK_MAGIC, 4);
set_level(u, SK_OBSERVATION, 3);
set_level(u, SK_STEALTH, 2);
set_level(u, SK_AUSDAUER, 1);
set_string(&u->name, "Seeschlange");
}
if ((rterrain(r) == T_GLACIER
|| rterrain(r) == T_SWAMP || rterrain(r) == T_DESERT)
&& rand() % 10000 < (5 + 100 * chaosfactor(r))) {
switch (rand() % 10) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
u = createunit(r, findfaction(MONSTER_FACTION), nrand(60, 20) + 1, new_race[RC_FIREDRAGON]);
break;
default:
u = createunit(r, findfaction(MONSTER_FACTION), nrand(30, 20) + 1, new_race[RC_DRAGON]);
break;
}
set_money(u, u->number * (rand() % 500 + 100));
set_level(u, SK_MAGIC, 4);
set_level(u, SK_OBSERVATION, 1+rand()%3);
set_level(u, SK_STEALTH, 1);
set_level(u, SK_AUSDAUER, 1);
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);
set_string(&u->lastorder, "WARTEN");
if (u->number == 1) {
sprintf(buf, "Es wurde ein %s gesichtet.",
LOC(default_locale, rc_name(u->race, 0)));
} else {
sprintf(buf, "Es wurden %d %s gesichtet.",
u->number, LOC(default_locale, rc_name(u->race, u->number!=1)));
}
addmessage(r, 0, buf, MSG_COMMENT, ML_IMPORTANT);
if (u->number == 1) {
sprintf(buf, "In %s wurde ein %s gesichtet.", regionid(r),
LOC(default_locale, rc_name(u->race, u->number!=1)));
} else {
sprintf(buf, "In %s wurden %d %s gesichtet.", regionid(r),
u->number, LOC(default_locale, rc_name(u->race, u->number!=1)));
}
for (u=r->units;u;u=u->next) freset(u->faction, FL_DH);
for (u=r->units;u;u=u->next) {
faction * f = u->faction;
if (!fval(f, FL_DH)) {
addmessage(0, f, buf, MSG_EVENT, ML_IMPORTANT);
fset(f, FL_DH);
}
}
}
}
/* Untote können entstehen */
for (r = regions; r; r = r->next) {
int unburied = deathcount(r);
if(is_cursed(r->attribs, C_HOLYGROUND, 0)) continue;
/* Chance 0.1% * chaosfactor */
if (r->land && unburied > r->land->peasants / 20 && rand() % 10000 < (100 + 100 * chaosfactor(r))) {
/* 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 / (rand() % 2 + 1);
const race * rc;
int i;
if (!undead || r->age < 20) continue;
switch(rand()%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, findfaction(MONSTER_FACTION), undead, rc);
if ((rc == new_race[RC_SKELETON] || rc == new_race[RC_ZOMBIE]) && rand()%10 < 4) {
set_item(u, I_RUSTY_SWORD, undead);
if (rand()%10 < 3) {
set_item(u, I_RUSTY_SHIELD, undead);
}
if (rand()%10 < 2) {
set_item(u, I_RUSTY_CHAIN_MAIL, undead);
}
}
for (i=0;i < MAXSKILLS;i++) {
if (rc->bonus[i] >= 1) {
set_level(u, SK_AUSDAUER, 1);
}
}
u->hp = unit_max_hp(u) * u->number;
deathcounts(r, -undead);
set_string(&u->lastorder, "WARTEN");
name_unit(u);
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 amount", r, undead);
add_message(&r->msgs, msg);
for (u=r->units;u;u=u->next) freset(u->faction, FL_DH);
for (u=r->units;u;u=u->next) {
if (fval(u->faction, FL_DH)) continue;
fset(u->faction, FL_DH);
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));
}
}
}
for (r = regions; r; r=r->next) {
for (u=r->units; u; u=u->next) {
if (u->faction->no != MONSTER_FACTION
&& (u->race->flags & RCF_DESERT)) {
if (fval(u, FL_ISNEW)) continue;
if (rand()%100 < 5) {
ADDMSG(&u->faction->msgs, msg_message("desertion",
"unit region", u, r));
u_setfaction(u, findfaction(MONSTER_FACTION));
}
}
}
}
/* Frühling, die Bäume schlagen aus. */
for (r = regions; r; r = r->next) {
if (fval(r, RF_CHAOTIC) ||(r->x >= -13 && r->x <= -6 && r->y >= 50 && r->y <= 57)) {
if (woodcount(r) >= 40 && rand()%100 < 33) {
#if GROWING_TREES
int trees = rtrees(r,2);
#else
int trees = rtrees(r);
#endif
int treemen = rand()%(max(50,trees)/3);
struct message * msg;
treemen = max(25, treemen);
woodcounts(r, -40);
trees = max(0, trees-treemen);
#if GROWING_TREES
rsettrees(r, 2, trees);
#else
rsettrees(r, trees);
#endif
u = createunit(r, findfaction(MONSTER_FACTION),treemen, new_race[RC_TREEMAN]);
set_string(&u->lastorder, "WARTEN");
/* guard(u, GUARD_ALL); kein auto-guard! erst in monster.c! */
set_level(u, SK_OBSERVATION, 2);
if (u->number == 1)
set_string(&u->name, "Ein wütender Ent");
else
set_string(&u->name, "Wütende Ents");
log_printf("%d Ents in %s.\n", u->number, regionname(r, NULL));
msg = msg_message("entrise", "region amount", r, u->number);
add_message(&r->msgs, msg);
for (u=r->units;u;u=u->next) freset(u->faction, FL_DH);
for (u=r->units;u;u=u->next) {
if (fval(u->faction, FL_DH)) continue;
fset(u->faction, FL_DH);
add_message(&u->faction->msgs, msg);
}
msg_release(msg);
}
}
}
/* Chaos */
for (r = regions; r; r = r->next) {
int i;
if (fval(r, RF_CHAOTIC)) {
chaos(r);
}
i = chaoscount(r);
if (!i) continue;
chaoscounts(r, -(int) (i * ((double) (rand() % 10)) / 100.0));
}
#ifdef HERBS_ROT
/* Kräuter verrotten */
for (r = regions; r; r = r->next) {
for (u = r->units; u; u=u->next) {
item **itmp = &u->items, *hbag = *i_find(&u->items, olditemtype[I_SACK_OF_CONSERVATION]);
int rot_chance = HERBROTCHANCE;
if (hbag) rot_chance = (HERBROTCHANCE*2)/5;
while (*itmp) {
item * itm = *itmp;
const herb_type * htype = resource2herb(itm->type->rtype);
int n = itm->number;
double k = n*rot_chance/100.0;
if (htype!=NULL) {
n = (int)(min(n, normalvariate(k, k/4)));
i_change(itmp, itm->type, -n);
}
if (itm==*itmp) itmp=&itm->next;
}
}
}
#endif
dissolve_units();
check_split();
check_luck();
}
#if NEW_LAEN
void growlaen(void) {
region *r;
regionlist *Berge=NULL, *rl;
unit *u;
int b=0, Laen, z, add;
short *add_laen;
for (r = regions; r; r = r->next) {
if (!fval(r, RF_CHAOTIC) &&
rterrain(r) == T_MOUNTAIN)
{
add_regionlist(&Berge,r);
b++;
}
}
add_laen=(short *)malloc(b*sizeof(short));
memset(add_laen, 0, b*sizeof(short));
Laen=b*MAXLAENPERTURN/14; /* Anzahl Berge * MAXLAENPERTURN/2 * Chance pro Berg */
while(Laen) { /* Laenverteilung über alle Berge */
z = rand()%b;
add = min(10 + rand() % 30, Laen);
add_laen[z] = (short)(add_laen[z]+add);
Laen -= add;
}
z=0;
for (rl=Berge; rl; rl=rl->next, z++) {
region * r = rl->region;
if (add_laen[z]>0) {
if (a_find(rl->region->attribs, &at_laen))
rsetlaen(rl->region, add_laen[z]+rlaen(rl->region));
}
else {
attrib *a=a_new(&at_laen);
struct message * msg = NULL;
a_add(&rl->region->attribs, a);
rsetlaen(rl->region, add_laen[z]);
/* Meldungen generieren */
for (u=r->units;u;u=u->next) freset(u->faction, FL_DH);
for (u = r->units; u; u = u->next ) {
if (!fval(u->faction, FL_DH) && eff_skill(u, SK_MINING, rl->region) >= olditemtype[I_LAEN]->minskill)
{
if (!msg) msg = msg_message("unveileog", "unit region", u, rl->region);
r_addmessage(rl->region, u->faction, msg);
fset(u->faction, FL_DH);
}
}
if (msg) msg_release(msg);
}
}
free(add_laen);
free_regionlist(Berge);
}
#endif