server/src/monsters.c

1018 lines
25 KiB
C
Raw Normal View History

2010-08-08 09:40:42 +02:00
/* 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<EFBFBD>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 <platform.h>
#include <kernel/config.h>
#include "economy.h"
#include "give.h"
#include "monster.h"
2010-08-08 09:40:42 +02:00
/* triggers includes */
#include <triggers/removecurse.h>
/* attributes includes */
#include <attributes/targetregion.h>
#include <attributes/hate.h>
/* kernel includes */
#include <kernel/build.h>
#include <kernel/curse.h>
2010-08-08 09:40:42 +02:00
#include <kernel/equipment.h>
#include <kernel/faction.h>
#include <kernel/item.h>
#include <kernel/messages.h>
2010-08-08 09:40:42 +02:00
#include <kernel/move.h>
#include <kernel/names.h>
#include <kernel/order.h>
#include <kernel/pathfinder.h>
#include <kernel/pool.h>
#include <kernel/race.h>
#include <kernel/region.h>
#include <kernel/reports.h>
#include <kernel/terrain.h>
#include <kernel/terrainid.h>
#include <kernel/unit.h>
/* util includes */
#include <util/attrib.h>
#include <util/base36.h>
#include <util/bsdstring.h>
#include <util/event.h>
#include <util/language.h>
#include <util/lists.h>
#include <util/log.h>
#include <util/rand.h>
#include <util/rng.h>
#include <quicklist.h>
2010-08-08 09:40:42 +02:00
/* libc includes */
#include <stdio.h>
#include <string.h>
#include <assert.h>
2011-03-07 08:03:10 +01:00
#define MOVECHANCE 25 /* chance fuer bewegung */
#define DRAGON_RANGE 20 /* Max. Distanz zum n<>chsten Drachenziel */
2010-08-08 09:40:42 +02:00
#define MAXILLUSION_TEXTS 3
2011-03-07 08:03:10 +01:00
static void reduce_weight(unit * u)
2010-08-08 09:40:42 +02:00
{
int capacity, weight = 0;
2011-03-07 08:03:10 +01:00
item **itmp = &u->items;
int horses = get_resource(u, get_resourcetype(R_HORSE));
2010-08-08 09:40:42 +02:00
if (horses > 0) {
2014-03-16 05:03:17 +01:00
horses = _min(horses, (u->number * 2));
change_resource(u, get_resourcetype(R_HORSE), -horses);
2010-08-08 09:40:42 +02:00
}
/* 0. ditch any vehicles */
2011-03-07 08:03:10 +01:00
while (*itmp != NULL) {
item *itm = *itmp;
const item_type *itype = itm->type;
weight += itm->number * itype->weight;
2010-08-08 09:40:42 +02:00
if (itype->flags & ITF_VEHICLE) {
give_item(itm->number, itm->type, u, NULL, NULL);
}
2011-03-07 08:03:10 +01:00
if (*itmp == itm)
itmp = &itm->next;
2010-08-08 09:40:42 +02:00
}
capacity = walkingcapacity(u);
/* 1. get rid of anything that isn't silver or really lightweight or helpful in combat */
2011-03-07 08:03:10 +01:00
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) {
2010-08-08 09:40:42 +02:00
if (itype->capacity < itype->weight) {
2014-03-16 05:03:17 +01:00
int reduce = _min(itm->number, -((capacity - weight) / itype->weight));
2010-08-08 09:40:42 +02:00
give_item(reduce, itm->type, u, NULL, NULL);
weight -= reduce * itype->weight;
}
}
}
2011-03-07 08:03:10 +01:00
if (*itmp == itm)
itmp = &itm->next;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
for (itmp = &u->items; *itmp && weight > capacity;) {
item *itm = *itmp;
const item_type *itype = itm->type;
weight += itm->number * itype->weight;
2010-08-08 09:40:42 +02:00
if (itype->capacity < itype->weight) {
2014-03-16 05:03:17 +01:00
int reduce = _min(itm->number, -((capacity - weight) / itype->weight));
2010-08-08 09:40:42 +02:00
give_item(reduce, itm->type, u, NULL, NULL);
weight -= reduce * itype->weight;
}
2011-03-07 08:03:10 +01:00
if (*itmp == itm)
itmp = &itm->next;
2010-08-08 09:40:42 +02:00
}
}
2011-03-07 08:03:10 +01:00
static order *monster_attack(unit * u, const unit * target)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
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;
2010-08-08 09:40:42 +02:00
return create_order(K_ATTACK, u->faction->locale, "%i", target->no);
}
2011-03-07 08:03:10 +01:00
static order *get_money_for_dragon(region * r, unit * u, int wanted)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
unit *u2;
int n;
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
/* attackiere bewachende einheiten */
2010-08-08 09:40:42 +02:00
for (u2 = r->units; u2; u2 = u2->next) {
if (u2 != u && is_guard(u2, GUARD_TAX)) {
2011-03-07 08:03:10 +01:00
order *ord = monster_attack(u, u2);
if (ord)
addlist(&u->orders, ord);
2010-08-08 09:40:42 +02:00
}
}
2011-03-07 08:03:10 +01:00
/* falls genug geld in der region ist, treiben wir steuern ein. */
2010-08-08 09:40:42 +02:00
if (rmoney(r) >= wanted) {
/* 5% chance, dass der drache aus einer laune raus attackiert */
if (chance(1.0 - u_race(u)->aggression)) {
2010-08-08 09:40:42 +02:00
return create_order(K_TAX, default_locale, NULL);
}
}
/* falls der drache launisch ist, oder das regionssilber knapp, greift er alle an */
2011-03-07 08:03:10 +01:00
n = 0;
2010-08-08 09:40:42 +02:00
for (u2 = r->units; u2; u2 = u2->next) {
if (u2->faction != u->faction && cansee(u->faction, r, u2, 0)) {
int m = get_money(u2);
2011-03-07 08:03:10 +01:00
if (m == 0 || is_guard(u2, GUARD_TAX))
continue;
2010-08-08 09:40:42 +02:00
else {
2011-03-07 08:03:10 +01:00
order *ord = monster_attack(u, u2);
2010-08-08 09:40:42 +02:00
if (ord) {
addlist(&u->orders, ord);
n += m;
}
}
}
}
/* falls die einnahmen erreicht werden, bleibt das monster noch eine
2011-03-07 08:03:10 +01:00
* runde hier. */
2010-08-08 09:40:42 +02:00
if (n + rmoney(r) >= wanted) {
return create_order(K_TAX, default_locale, NULL);
}
/* wenn wir NULL zur<75>ckliefern, macht der drache was anderes, z.b. weggehen */
return NULL;
}
2011-03-07 08:03:10 +01:00
static int all_money(region * r, faction * f)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
unit *u;
int m;
m = rmoney(r);
2010-08-08 09:40:42 +02:00
for (u = r->units; u; u = u->next) {
2011-03-07 08:03:10 +01:00
if (f != u->faction) {
2010-08-08 09:40:42 +02:00
m += get_money(u);
}
2011-03-07 08:03:10 +01:00
}
return m;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static direction_t richest_neighbour(region * r, faction * f, int absolut)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
/* m - maximum an Geld, d - Richtung, i - index, t = Geld hier */
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
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 */
2010-08-08 09:40:42 +02:00
for (i = 0; i != MAXDIRECTIONS; i++) {
2011-03-07 08:03:10 +01:00
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;
}
2010-08-08 09:40:42 +02:00
}
}
2011-03-07 08:03:10 +01:00
return d;
2010-08-08 09:40:42 +02:00
}
static bool room_for_race_in_region(region * r, const race * rc)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
unit *u;
int c = 0;
for (u = r->units; u; u = u->next) {
if (u_race(u) == rc)
2011-03-07 08:03:10 +01:00
c += u->number;
}
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
if (c > (rc->splitsize * 2))
return false;
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
return true;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static direction_t random_neighbour(region * r, unit * u)
2010-08-08 09:40:42 +02:00
{
int i;
2011-03-07 08:03:10 +01:00
region *rc;
region * next[MAXDIRECTIONS];
2011-03-07 08:03:10 +01:00
int rr, c = 0, c2 = 0;
get_neighbours(r, next);
2011-03-07 08:03:10 +01:00
/* Nachsehen, wieviele Regionen in Frage kommen */
for (i = 0; i != MAXDIRECTIONS; i++) {
rc = next[i];
2011-03-07 08:03:10 +01:00
if (rc && can_survive(u, rc)) {
if (room_for_race_in_region(rc, u_race(u))) {
2011-03-07 08:03:10 +01:00
c++;
}
c2++;
}
}
if (c == 0) {
if (c2 == 0) {
return NODIRECTION;
} else {
c = c2;
c2 = 0; /* c2 == 0 -> room_for_race nicht beachten */
}
}
/* Zuf<75>llig eine ausw<73>hlen */
rr = rng_int() % c;
/* Durchz<68>hlen */
c = -1;
for (i = 0; i != MAXDIRECTIONS; i++) {
rc = next[i];
2011-03-07 08:03:10 +01:00
if (rc && can_survive(u, rc)) {
if (c2 == 0) {
c++;
} else if (room_for_race_in_region(rc, u_race(u))) {
2011-03-07 08:03:10 +01:00
c++;
}
if (c == rr)
return (direction_t)i;
2011-03-07 08:03:10 +01:00
}
}
assert(1 == 0); /* Bis hierhin sollte er niemals kommen. */
return NODIRECTION;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static direction_t treeman_neighbour(region * r)
2010-08-08 09:40:42 +02:00
{
int i;
2011-03-07 08:03:10 +01:00
int rr;
int c = 0;
region * next[MAXDIRECTIONS];
2011-03-07 08:03:10 +01:00
get_neighbours(r, next);
2011-03-07 08:03:10 +01:00
/* Nachsehen, wieviele Regionen in Frage kommen */
for (i = 0; i != MAXDIRECTIONS; i++) {
if (next[i] && rterrain(next[i]) != T_OCEAN
&& rterrain(next[i]) != T_GLACIER && rterrain(next[i]) != T_DESERT) {
++c;
2011-03-07 08:03:10 +01:00
}
}
if (c == 0) {
return NODIRECTION;
}
/* Zuf<75>llig eine ausw<73>hlen */
rr = rng_int() % c;
/* Durchz<68>hlen */
c = -1;
for (i = 0; i != MAXDIRECTIONS; i++) {
if (next[i] && rterrain(next[i]) != T_OCEAN
&& rterrain(next[i]) != T_GLACIER && rterrain(next[i]) != T_DESERT) {
if (++c == rr) {
return (direction_t)i;
2011-03-07 08:03:10 +01:00
}
}
}
assert(!"this should never happen"); /* Bis hierhin sollte er niemals kommen. */
2011-03-07 08:03:10 +01:00
return NODIRECTION;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static order *monster_move(region * r, unit * u)
2010-08-08 09:40:42 +02:00
{
direction_t d = NODIRECTION;
if (monster_is_waiting(u)) {
2011-03-07 08:03:10 +01:00
return NULL;
}
switch (old_race(u_race(u))) {
2011-03-08 08:42:31 +01:00
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;
2010-08-08 09:40:42 +02:00
}
/* falls kein geld gefunden wird, zufaellig verreisen, aber nicht in
2011-03-07 08:03:10 +01:00
* den ozean */
2010-08-08 09:40:42 +02:00
if (d == NODIRECTION)
return NULL;
reduce_weight(u);
2011-03-08 08:42:31 +01:00
return create_order(K_MOVE, u->faction->locale, "%s",
LOC(u->faction->locale, directions[d]));
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static int dragon_affinity_value(region * r, unit * u)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
int m = all_money(r, u->faction);
2010-08-08 09:40:42 +02:00
if (u_race(u) == get_race(RC_FIREDRAGON)) {
2011-03-07 08:03:10 +01:00
return (int)(normalvariate(m, m / 2));
} else {
return (int)(normalvariate(m, m / 4));
}
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static attrib *set_new_dragon_target(unit * u, region * r, int range)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
int max_affinity = 0;
region *max_region = NULL;
quicklist *ql, *rlist = regions_in_range(r, range, allowed_dragon);
int qi;
2010-08-08 09:40:42 +02:00
for (qi=0, ql = rlist; ql; ql_advance(&ql, &qi, 1)) {
region *r2 = (region *)ql_get(ql, qi);
2010-08-08 09:40:42 +02:00
int affinity = dragon_affinity_value(r2, u);
if (affinity > max_affinity) {
max_affinity = affinity;
max_region = r2;
}
}
ql_free(rlist);
2011-03-07 08:03:10 +01:00
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;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static order *make_movement_order(unit * u, const region * target, int moves,
bool(*allowed) (const region *, const region *))
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
region *r = u->region;
region **plan;
2010-08-08 09:40:42 +02:00
int bytes, position = 0;
2011-03-07 08:03:10 +01:00
char zOrder[128], *bufp = zOrder;
2010-08-08 09:40:42 +02:00
size_t size = sizeof(zOrder) - 1;
2011-03-07 08:03:10 +01:00
if (monster_is_waiting(u))
return NULL;
plan = path_find(r, target, DRAGON_RANGE * 5, allowed);
if (plan == NULL)
return NULL;
bytes =
2011-03-08 08:42:31 +01:00
(int)strlcpy(bufp,
2014-06-17 08:05:39 +02:00
(const char *)LOC(u->faction->locale, keyword(K_MOVE)), size);
2011-03-07 08:03:10 +01:00
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) {
2010-08-08 09:40:42 +02:00
*bufp++ = ' ';
--size;
}
2011-03-07 08:03:10 +01:00
bytes =
2011-03-08 08:42:31 +01:00
(int)strlcpy(bufp,
(const char *)LOC(u->faction->locale, directions[dir]), size);
2011-03-07 08:03:10 +01:00
if (wrptr(&bufp, &size, bytes) != 0)
WARN_STATIC_BUFFER();
}
2010-08-08 09:40:42 +02:00
*bufp = 0;
2011-03-07 08:03:10 +01:00
return parse_order(zOrder, u->faction->locale);
2010-08-08 09:40:42 +02:00
}
#ifdef TODO_ALP
2011-03-07 08:03:10 +01:00
static order *monster_seeks_target(region * r, unit * u)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
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(u))) {
2011-03-08 08:42:31 +01:00
case RC_ALP:
target = alp_target(u);
break;
default:
assert(!"Seeker-Monster gibt kein Ziel an");
2011-03-07 08:03:10 +01:00
}
/* TODO: pr<70>fen, ob target <20>berhaupt noch existiert... */
2010-08-08 09:40:42 +02:00
if (!target) {
log_error("Monster '%s' hat kein Ziel!\n", unitname(u));
2011-03-07 08:03:10 +01:00
return NULL; /* this is a bug workaround! remove!! */
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
if (r == target->region) { /* Wir haben ihn! */
if (u_race(u) == get_race(RC_ALP)) {
2010-08-08 09:40:42 +02:00
alp_findet_opfer(u, r);
2011-03-07 08:03:10 +01:00
} else {
2010-08-08 09:40:42 +02:00
assert(!"Seeker-Monster hat keine Aktion fuer Ziel");
}
return NULL;
}
2011-03-07 08:03:10 +01:00
2010-08-08 09:40:42 +02:00
/* Simpler Ansatz: Nachbarregion mit gerinster Distanz suchen.
* Sinnvoll momentan nur bei Monstern, die sich nicht um das
* Terrain k<EFBFBD>mmern. Nebelw<EFBFBD>nde & Co machen derzeit auch nix...
*/
dist2 = distance(r, target->region);
d = NODIRECTION;
2011-03-07 08:03:10 +01:00
for (i = 0; i < MAXDIRECTIONS; i++) {
2010-08-08 09:40:42 +02:00
nr = rconnect(r, i);
assert(nr);
dist = distance(nr, target->region);
2011-03-07 08:03:10 +01:00
if (dist < dist2) {
2010-08-08 09:40:42 +02:00
dist2 = dist;
d = i;
}
}
2011-03-07 08:03:10 +01:00
assert(d != NODIRECTION);
2011-03-08 08:42:31 +01:00
return create_order(K_MOVE, u->faction->locale, "%s",
LOC(u->faction->locale, directions[d]));
2010-08-08 09:40:42 +02:00
}
#endif
2011-03-07 08:03:10 +01:00
static void monster_attacks(unit * u)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
region *r = u->region;
unit *u2;
for (u2 = r->units; u2; u2 = u2->next) {
if (cansee(u->faction, r, u2, 0) && u2->faction != u->faction
&& chance(0.75)) {
order *ord = monster_attack(u, u2);
if (ord)
addlist(&u->orders, ord);
2010-08-08 09:40:42 +02:00
}
}
}
2011-03-07 08:03:10 +01:00
static const char *random_growl(void)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
switch (rng_int() % 5) {
2011-03-08 08:42:31 +01:00
case 0:
return "Groammm";
case 1:
return "Roaaarrrr";
case 2:
return "Chhhhhhhhhh";
case 3:
return "Tschrrrkk";
case 4:
return "Schhhh";
2011-03-07 08:03:10 +01:00
}
return "";
2010-08-08 09:40:42 +02:00
}
extern struct attrib_type at_direction;
2011-03-07 08:03:10 +01:00
static order *monster_learn(unit * u)
2010-08-08 09:40:42 +02:00
{
int c = 0;
int n;
2011-03-07 08:03:10 +01:00
skill *sv;
const struct locale *lang = u->faction->locale;
2010-08-08 09:40:42 +02:00
/* Monster lernt ein zuf<75>lliges Talent aus allen, in denen es schon
* Lerntage hat. */
2011-03-07 08:03:10 +01:00
2010-08-08 09:40:42 +02:00
for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) {
2011-03-07 08:03:10 +01:00
if (sv->level > 0)
++c;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
if (c == 0)
return NULL;
n = rng_int() % c + 1;
2010-08-08 09:40:42 +02:00
c = 0;
2011-03-07 08:03:10 +01:00
2010-08-08 09:40:42 +02:00
for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) {
2011-03-07 08:03:10 +01:00
if (sv->level > 0) {
2010-08-08 09:40:42 +02:00
if (++c == n) {
return create_order(K_STUDY, lang, "'%s'", skillname(sv->id, lang));
}
}
}
return NULL;
}
static bool check_overpopulated(unit * u)
2010-08-08 09:40:42 +02:00
{
unit *u2;
int c = 0;
2011-03-07 08:03:10 +01:00
for (u2 = u->region->units; u2; u2 = u2->next) {
if (u_race(u2) == u_race(u) && u != u2)
2011-03-07 08:03:10 +01:00
c += u2->number;
2010-08-08 09:40:42 +02:00
}
if (c > u_race(u)->splitsize * 2)
2011-03-07 08:03:10 +01:00
return true;
2010-08-08 09:40:42 +02:00
return false;
}
2011-03-07 08:03:10 +01:00
static void recruit_dracoids(unit * dragon, int size)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
faction *f = dragon->faction;
region *r = dragon->region;
const struct item *weapon = NULL;
order *new_order = NULL;
unit *un = createunit(r, f, size, get_race(RC_DRACOID));
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
fset(un, UFL_ISNEW | UFL_MOVED);
2010-08-08 09:40:42 +02:00
name_unit(un);
change_money(dragon, -un->number * 50);
equip_unit(un, get_equipment("recruited_dracoid"));
setstatus(un, ST_FIGHT);
2011-03-07 08:03:10 +01:00
for (weapon = un->items; weapon; weapon = weapon->next) {
const weapon_type *wtype = weapon->type->rtype->wtype;
2010-08-08 09:40:42 +02:00
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));
}
2011-03-07 08:03:10 +01:00
if (new_order != NULL) {
2010-08-08 09:40:42 +02:00
addlist(&un->orders, new_order);
}
}
2011-03-07 08:03:10 +01:00
static order *plan_dragon(unit * u)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
attrib *ta = a_find(u->attribs, &at_targetregion);
region *r = u->region;
region *tr = NULL;
bool move = false;
2011-03-07 08:03:10 +01:00
order *long_order = NULL;
2010-08-08 09:40:42 +02:00
reduce_weight(u);
2011-03-07 08:03:10 +01:00
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 */
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
move |= chance(0.04); /* 4% chance to change your mind */
if (u_race(u) == get_race(RC_WYRM) && !move) {
2011-03-07 08:03:10 +01:00
unit *u2;
for (u2 = r->units; u2; u2 = u2->next) {
2010-08-08 09:40:42 +02:00
/* wyrme sind einzelg<6C>nger */
2011-03-07 08:03:10 +01:00
if (u2 == u) {
2010-08-08 09:40:42 +02:00
/* we do not make room for newcomers, so we don't need to look at them */
break;
}
if (u2 != u && u_race(u2) == u_race(u) && chance(0.5)) {
2010-08-08 09:40:42 +02:00
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);
2011-03-07 08:03:10 +01:00
} else
ta = a_find(u->attribs, &at_targetregion);
if (ta != NULL) {
2010-08-08 09:40:42 +02:00
tr = (region *) ta->data.v;
2011-03-07 08:03:10 +01:00
if (tr == NULL || !path_exists(u->region, tr, DRAGON_RANGE, allowed_dragon)) {
2010-08-08 09:40:42 +02:00
ta = set_new_dragon_target(u, u->region, DRAGON_RANGE);
2011-03-07 08:03:10 +01:00
if (ta)
tr = findregion(ta->data.sa[0], ta->data.sa[1]);
2010-08-08 09:40:42 +02:00
}
}
2011-03-07 08:03:10 +01:00
if (tr != NULL) {
assert(long_order == NULL);
switch (old_race(u_race(u))) {
2011-03-08 08:42:31 +01:00
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;
default:
break;
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
if (rng_int() % 100 < 15) {
const struct locale *lang = u->faction->locale;
2010-08-08 09:40:42 +02:00
/* do a growl */
if (rname(tr, lang)) {
2011-03-08 08:42:31 +01:00
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",
2011-03-07 08:03:10 +01:00
"etwas in", rname(tr, u->faction->locale)));
2010-08-08 09:40:42 +02:00
}
}
} else {
/* we have no target. do we like it here, then? */
long_order = get_money_for_dragon(u->region, u, income(u));
2011-03-07 08:03:10 +01:00
if (long_order == NULL) {
2010-08-08 09:40:42 +02:00
/* money is gone, need a new target */
set_new_dragon_target(u, u->region, DRAGON_RANGE);
}
else if (u_race(u) != get_race(RC_FIREDRAGON)) {
2010-08-08 09:40:42 +02:00
/* 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);
}
}
}
}
2011-03-07 08:03:10 +01:00
if (long_order == NULL) {
2010-08-08 09:40:42 +02:00
skill_t sk = SK_PERCEPTION;
/* study perception (or a random useful skill) */
while (!skill_enabled(sk) || u_race(u)->bonus[sk] < -5) {
2011-03-07 08:03:10 +01:00
sk = (skill_t) (rng_int() % MAXSKILLS);
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
long_order = create_order(K_STUDY, u->faction->locale, "'%s'",
2010-08-08 09:40:42 +02:00
skillname(sk, u->faction->locale));
}
return long_order;
}
2011-03-07 08:03:10 +01:00
void plan_monsters(faction * f)
2010-08-08 09:40:42 +02:00
{
region *r;
assert(f);
f->lastorders = turn;
for (r = regions; r; r = r->next) {
unit *u;
double attack_chance = MONSTERATTACK;
bool attacking = false;
2010-08-08 09:40:42 +02:00
for (u = r->units; u; u = u->next) {
2011-03-07 08:03:10 +01:00
attrib *ta;
order *long_order = NULL;
2010-08-08 09:40:42 +02:00
/* Ab hier nur noch Befehle f<>r NPC-Einheiten. */
2011-03-07 08:03:10 +01:00
if (!is_monsters(u->faction))
continue;
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
if (attack_chance > 0.0) {
if (chance(attack_chance))
attacking = true;
2010-08-08 09:40:42 +02:00
attack_chance = 0.0;
}
2011-03-07 08:03:10 +01:00
if (u->status > ST_BEHIND) {
2010-08-08 09:40:42 +02:00
setstatus(u, ST_FIGHT);
/* all monsters fight */
}
if (skill_enabled(SK_PERCEPTION)) {
2010-08-08 09:40:42 +02:00
/* 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)) {
2011-03-07 08:03:10 +01:00
unit *tu = (unit *) ta->data.v;
if (tu && tu->region == r) {
2011-03-08 08:42:31 +01:00
addlist(&u->orders,
create_order(K_ATTACK, u->faction->locale, "%i", tu->no));
2010-08-08 09:40:42 +02:00
} else if (tu) {
tu = findunitg(ta->data.i, NULL);
2011-03-07 08:03:10 +01:00
if (tu != NULL) {
2010-08-08 09:40:42 +02:00
long_order = make_movement_order(u, tu->region, 2, allowed_walk);
}
2011-03-07 08:03:10 +01:00
} else
a_remove(&u->attribs, ta);
2010-08-08 09:40:42 +02:00
}
/* 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: */
2011-03-07 08:03:10 +01:00
if (long_order == NULL) {
attrib *ta = a_find(u->attribs, &at_targetregion);
2010-08-08 09:40:42 +02:00
if (ta) {
2011-03-07 08:03:10 +01:00
if (u->region == (region *) ta->data.v) {
2010-08-08 09:40:42 +02:00
a_remove(&u->attribs, ta);
}
} else if (u_race(u)->flags & RCF_MOVERANDOM) {
2011-03-07 08:03:10 +01:00
if (rng_int() % 100 < MOVECHANCE || check_overpopulated(u)) {
2010-08-08 09:40:42 +02:00
long_order = monster_move(r, u);
}
}
}
2011-03-07 08:03:10 +01:00
if (long_order == NULL) {
2010-08-08 09:40:42 +02:00
/* Einheiten, die Waffenlosen Kampf lernen k<>nnten, lernen es um
2011-03-07 08:03:10 +01:00
* zu bewachen: */
if (u_race(u)->bonus[SK_WEAPONLESS] != -99) {
2010-08-08 09:40:42 +02:00
if (eff_skill(u, SK_WEAPONLESS, u->region) < 1) {
2011-03-07 08:03:10 +01:00
long_order =
2011-03-08 08:42:31 +01:00
create_order(K_STUDY, f->locale, "'%s'",
skillname(SK_WEAPONLESS, f->locale));
2010-08-08 09:40:42 +02:00
}
}
}
2011-03-07 08:03:10 +01:00
if (long_order == NULL) {
2010-08-08 09:40:42 +02:00
/* Ab hier noch nicht generalisierte Spezialbehandlungen. */
if (!u->orders) {
handle_event(u->attribs, "ai_move", u);
}
switch (old_race(u_race(u))) {
2011-03-08 08:42:31 +01:00
case RC_SEASERPENT:
long_order = create_order(K_PIRACY, f->locale, NULL);
break;
2010-08-08 09:40:42 +02:00
#ifdef TODO_ALP
2011-03-08 08:42:31 +01:00
case RC_ALP:
long_order = monster_seeks_target(r, u);
break;
2010-08-08 09:40:42 +02:00
#endif
2011-03-08 08:42:31 +01:00
case RC_FIREDRAGON:
case RC_DRAGON:
case RC_WYRM:
long_order = plan_dragon(u);
break;
default:
if (u_race(u)->flags & RCF_LEARN) {
2011-03-08 08:42:31 +01:00
long_order = monster_learn(u);
}
break;
2010-08-08 09:40:42 +02:00
}
}
if (long_order) {
addlist(&u->orders, long_order);
}
}
}
pathfinder_cleanup();
}
2011-03-07 08:03:10 +01:00
static double chaosfactor(region * r)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
attrib *a = a_find(r->attribs, &at_chaoscount);
if (!a)
return 0;
return ((double)a->data.i / 1000.0);
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
static int nrand(int start, int sub)
2010-08-08 09:40:42 +02:00
{
int res = 0;
do {
if (rng_int() % 100 < start)
res++;
start -= sub;
2011-03-08 08:42:31 +01:00
}
while (start > 0);
2010-08-08 09:40:42 +02:00
return res;
}
/** Drachen und Seeschlangen k<>nnen entstehen */
2011-03-07 08:03:10 +01:00
void spawn_dragons(void)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
region *r;
faction *monsters = get_monsters();
2010-08-08 09:40:42 +02:00
for (r = regions; r; r = r->next) {
2011-03-07 08:03:10 +01:00
unit *u;
2010-08-08 09:40:42 +02:00
2011-03-07 08:03:10 +01:00
if (fval(r->terrain, SEA_REGION) && rng_int() % 10000 < 1) {
u = createunit(r, monsters, 1, get_race(RC_SEASERPENT));
2011-03-07 08:03:10 +01:00
fset(u, UFL_ISNEW | UFL_MOVED);
2010-08-08 09:40:42 +02:00
equip_unit(u, get_equipment("monster_seaserpent"));
}
2011-03-07 08:03:10 +01:00
if ((r->terrain == newterrain(T_GLACIER)
|| r->terrain == newterrain(T_SWAMP)
|| r->terrain == newterrain(T_DESERT))
&& rng_int() % 10000 < (5 + 100 * chaosfactor(r))) {
2010-08-08 09:40:42 +02:00
if (chance(0.80)) {
u = createunit(r, monsters, nrand(60, 20) + 1, get_race(RC_FIREDRAGON));
2010-08-08 09:40:42 +02:00
} else {
u = createunit(r, monsters, nrand(30, 20) + 1, get_race(RC_DRAGON));
2010-08-08 09:40:42 +02:00
}
2011-03-07 08:03:10 +01:00
fset(u, UFL_ISNEW | UFL_MOVED);
2010-08-08 09:40:42 +02:00
equip_unit(u, get_equipment("monster_dragon"));
2011-03-07 08:03:10 +01:00
if (verbosity >= 2) {
log_printf(stdout, "%d %s in %s.\n", u->number,
2011-03-08 08:42:31 +01:00
LOC(default_locale,
rc_name(u_race(u), u->number != 1)), regionname(r, NULL));
2010-08-08 09:40:42 +02:00
}
name_unit(u);
/* add message to the region */
ADDMSG(&r->msgs,
msg_message("sighting", "region race number", r, u_race(u), u->number));
2010-08-08 09:40:42 +02:00
}
}
}
/** Untote k<>nnen entstehen */
2011-03-07 08:03:10 +01:00
void spawn_undead(void)
2010-08-08 09:40:42 +02:00
{
2011-03-07 08:03:10 +01:00
region *r;
faction *monsters = get_monsters();
2010-08-08 09:40:42 +02:00
for (r = regions; r; r = r->next) {
int unburied = deathcount(r);
2011-03-07 08:03:10 +01:00
static const curse_type *ctype = NULL;
if (!ctype)
ctype = ct_find("holyground");
if (ctype && curse_active(get_curse(r->attribs, ctype)))
continue;
2010-08-08 09:40:42 +02:00
/* Chance 0.1% * chaosfactor */
2011-03-07 08:03:10 +01:00
if (r->land && unburied > r->land->peasants / 20
&& rng_int() % 10000 < (100 + 100 * chaosfactor(r))) {
unit *u;
2010-08-08 09:40:42 +02:00
/* es ist sinnfrei, wenn irgendwo im Wald 3er-Einheiten Untote entstehen.
2011-03-07 08:03:10 +01:00
* Lieber sammeln lassen, bis sie mindestens 5% der Bev<EFBFBD>lkerung sind, und
* dann erst auferstehen. */
2010-08-08 09:40:42 +02:00
int undead = unburied / (rng_int() % 2 + 1);
2011-03-07 08:03:10 +01:00
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) {
2011-03-08 08:42:31 +01:00
case 0:
rc = get_race(RC_SKELETON);
2011-03-08 08:42:31 +01:00
break;
case 1:
rc = get_race(RC_ZOMBIE);
2011-03-08 08:42:31 +01:00
break;
default:
rc = get_race(RC_GHOUL);
2011-03-08 08:42:31 +01:00
break;
2010-08-08 09:40:42 +02:00
}
u = createunit(r, monsters, undead, rc);
2011-03-07 08:03:10 +01:00
fset(u, UFL_ISNEW | UFL_MOVED);
if ((rc == get_race(RC_SKELETON) || rc == get_race(RC_ZOMBIE))
2011-03-07 08:03:10 +01:00
&& rng_int() % 10 < 4) {
2010-08-08 09:40:42 +02:00
equip_unit(u, get_equipment("rising_undead"));
}
2011-03-07 08:03:10 +01:00
for (i = 0; i < MAXSKILLS; i++) {
2010-08-08 09:40:42 +02:00
if (rc->bonus[i] >= 1) {
2011-03-07 08:03:10 +01:00
set_level(u, (skill_t) i, 1);
2010-08-08 09:40:42 +02:00
}
}
u->hp = unit_max_hp(u) * u->number;
deathcounts(r, -undead);
name_unit(u);
2011-03-07 08:03:10 +01:00
if (verbosity >= 2) {
log_printf(stdout, "%d %s in %s.\n", u->number,
2011-03-08 08:42:31 +01:00
LOC(default_locale,
rc_name(u_race(u), u->number != 1)), regionname(r, NULL));
2010-08-08 09:40:42 +02:00
}
{
2011-03-07 08:03:10 +01:00
message *msg = msg_message("undeadrise", "region", r);
2010-08-08 09:40:42 +02:00
add_message(&r->msgs, msg);
2011-03-07 08:03:10 +01:00
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;
2010-08-08 09:40:42 +02:00
fset(u->faction, FFL_SELECT);
add_message(&u->faction->msgs, msg);
}
msg_release(msg);
}
} else {
int i = deathcount(r);
if (i) {
/* Gr<47>ber verwittern, 3% der Untoten finden die ewige Ruhe */
2011-03-07 08:03:10 +01:00
deathcounts(r, (int)(-i * 0.03));
2010-08-08 09:40:42 +02:00
}
}
}
}