server/src/common/kernel/build.c
Enno Rehling e1dc630bcd double commit (I know it's bad, but I messed up):
http://eressea.upb.de/mantis/view.php?id=595
Durchreise von unsichtbaren Einheiten wird angezeigt
- invisible() war an vielen Stellen in ein #if geklammert, das dazu führte, das der alte ROI nicht funktioniert.

http://eressea.upb.de/mantis/view.php?id=581
Bewachen von Untoten (Skelette, Skelettherren, Dämonen)
- Untote lernen jetzt in der ersten Woche waffenlosen Kampf, und bewachen damit. Der alte CANGUARD Hack wird vorerst von keinem Monster mehr genutzt.

ACHTUNG! Die letztgenannte Änderung führt dazu dass Monster in der kommenden Woche evtl. aufhören, Regionen zu bewachen, bis sie's wieder gelernt haben.
2005-07-10 16:32:47 +00:00

1295 lines
34 KiB
C

/* vi: set ts=2:
*
* Eressea PB(E)M host Copyright (C) 1998-2003
* Christian Schlittchen (corwin@amber.kn-bremen.de)
* Katja Zedel (katze@felidae.kn-bremen.de)
* Henning Peters (faroul@beyond.kn-bremen.de)
* Enno Rehling (enno@eressea-pbem.de)
* Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
*
* based on:
*
* Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace
* Atlantis v1.7 Copyright 1996 by Alex Schröder
*
* This program may not be used, modified or distributed without
* prior permission by the authors of Eressea.
* This program may not be sold or used commercially without prior written
* permission from the authors.
*/
#include <config.h>
#include "eressea.h"
#include "build.h"
/* kernel includes */
#include "alchemy.h"
#include "border.h"
#include "building.h"
#include "curse.h"
#include "faction.h"
#include "group.h"
#include "item.h"
#include "magic.h"
#include "message.h"
#include "movement.h"
#include "order.h"
#include "pool.h"
#include "race.h"
#include "region.h"
#include "ship.h"
#include "skill.h"
#include "unit.h"
/* from libutil */
#include <attrib.h>
#include <base36.h>
#include <event.h>
#include <goodies.h>
#include <resolve.h>
#include <xml.h>
/* from libc */
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* attributes inclues */
#include <attributes/matmod.h>
#define STONERECYCLE 50
/* Name, MaxGroesse, MinBauTalent, Kapazitaet, {Eisen, Holz, Stein, BauSilber,
* Laen, Mallorn}, UnterSilber, UnterSpezialTyp, UnterSpezial */
static boolean
CheckOverload(void)
{
static int value = -1;
if (value<0) {
const char * str = get_param(global.parameters, "rules.check_overload");
value = str?atoi(str):0;
}
return value;
}
static int
slipthru(const region * r, const unit * u, const building * b)
{
unit *u2;
int n, o;
/* b ist die burg, in die man hinein oder aus der man heraus will. */
if (!b) {
return 1;
}
if (b->besieged < b->size * SIEGEFACTOR) {
return 1;
}
/* u wird am hinein- oder herausschluepfen gehindert, wenn STEALTH <=
* OBSERVATION +2 der belagerer u2 ist */
n = eff_skill(u, SK_STEALTH, r);
for (u2 = r->units; u2; u2 = u2->next)
if (usiege(u2) == b) {
if (invisible(u, u2) >= u->number) continue;
o = eff_skill(u2, SK_OBSERVATION, r);
if (o + 2 >= n)
return 0; /* entdeckt! */
}
return 1;
}
boolean
can_contact(const region * r, const unit * u, const unit * u2)
{
/* hier geht es nur um die belagerung von burgen */
if (u->building == u2->building)
return true;
/* unit u is trying to contact u2 - unasked for contact. wenn u oder u2
* nicht in einer burg ist, oder die burg nicht belagert ist, ist
* slipthru () == 1. ansonsten ist es nur 1, wenn man die belagerer */
if (slipthru(u->region, u, u->building)
&& slipthru(u->region, u2, u2->building))
return true;
if (alliedunit(u, u2->faction, HELP_GIVE))
return true;
return false;
}
static void
contact_cmd(unit * u, order * ord, boolean tries)
{
/* unit u kontaktiert unit u2. Dies setzt den contact einfach auf 1 -
* ein richtiger toggle ist (noch?) nicht noetig. die region als
* parameter ist nur deswegen wichtig, weil er an getunit ()
* weitergegeben wird. dies wird fuer das auffinden von tempunits in
* getnewunit () verwendet! */
unit *u2;
region * r = u->region;
init_tokens(ord);
skip_token();
u2 = getunitg(r, u->faction);
if (u2!=NULL) {
if (!can_contact(r, u, u2)) {
if (tries) cmistake(u, u->thisorder, 23, MSG_EVENT);
return;
}
usetcontact(u, u2);
}
}
/* ------------------------------------------------------------- */
/* ------------------------------------------------------------- */
/* ------------------------------------------------------------- */
struct building *
getbuilding(const struct region * r)
{
building * b = findbuilding(getid());
if (b==NULL || r!=b->region) return NULL;
return b;
}
ship *
getship(const struct region * r)
{
ship *sh, *sx = findship(getshipid());
for (sh = r->ships; sh; sh = sh->next) {
if (sh == sx) return sh;
}
return NULL;
}
/* ------------------------------------------------------------- */
static void
siege_cmd(unit * u, order * ord)
{
region * r = u->region;
unit *u2;
building *b;
int d;
int bewaffnete, katapultiere = 0;
static boolean init = false;
static const curse_type * magicwalls_ct;
static item_type * it_catapultammo = NULL;
if (!init) {
init = true;
magicwalls_ct = ct_find("magicwalls");
it_catapultammo = it_find("catapultammo");
}
/* gibt es ueberhaupt Burgen? */
init_tokens(ord);
skip_token();
b = getbuilding(r);
if (!b) {
cmistake(u, ord, 31, MSG_BATTLE);
return;
}
if (!playerrace(u->race)) {
/* keine Drachen, Illusionen, Untote etc */
cmistake(u, ord, 166, MSG_BATTLE);
return;
}
/* schaden durch katapulte */
d = min(u->number,
min(new_get_pooled(u, it_catapultammo->rtype, GET_SLACK|GET_RESERVE|GET_POOLED_SLACK), get_item(u, I_CATAPULT)));
if (eff_skill(u, SK_CATAPULT, r) >= 1) {
katapultiere = d;
d *= eff_skill(u, SK_CATAPULT, r);
} else {
d = 0;
}
if ((bewaffnete = armedmen(u)) == 0 && d == 0) {
/* abbruch, falls unbewaffnet oder unfaehig, katapulte zu benutzen */
cmistake(u, ord, 80, MSG_EVENT);
return;
}
if (!(getguard(u) & GUARD_TRAVELTHRU)) {
/* abbruch, wenn die einheit nicht vorher die region bewacht - als
* warnung fuer alle anderen! */
cmistake(u, ord, 81, MSG_EVENT);
return;
}
/* einheit und burg markieren - spart zeit beim behandeln der einheiten
* in der burg, falls die burg auch markiert ist und nicht alle
* einheiten wieder abgesucht werden muessen! */
usetsiege(u, b);
b->besieged += max(bewaffnete, katapultiere);
/* definitiver schaden eingeschraenkt */
d = min(d, b->size - 1);
/* meldung, schaden anrichten */
if (d && !curse_active(get_curse(b->attribs, magicwalls_ct))) {
b->size -= d;
new_use_pooled(u, it_catapultammo->rtype, GET_SLACK|GET_RESERVE|GET_POOLED_SLACK, d);
d = 100 * d / b->size;
} else d = 0;
/* meldung fuer belagerer */
ADDMSG(&u->faction->msgs, msg_message("siege",
"unit building destruction", u, b, d));
for (u2 = r->units; u2; u2 = u2->next) freset(u2->faction, FL_DH);
fset(u->faction, FL_DH);
/* Meldung fuer Burginsassen */
for (u2 = r->units; u2; u2 = u2->next) {
if (u2->building == b && !fval(u2->faction, FL_DH)) {
fset(u2->faction, FL_DH);
ADDMSG(&u2->faction->msgs, msg_message("siege",
"unit building destruction", u, b, d));
}
}
}
void
do_siege(void)
{
region *r;
for (r = regions; r; r = r->next) {
if (rterrain(r) != T_OCEAN) {
unit *u;
for (u = r->units; u; u = u->next) {
if (get_keyword(u->thisorder) == K_BESIEGE) {
siege_cmd(u, u->thisorder);
}
}
}
}
}
/* ------------------------------------------------------------- */
static void
destroy_road(unit *u, int nmax, struct order * ord)
{
direction_t d = getdirection(u->faction->locale);
unit *u2;
region *r = u->region;
short n = (short)nmax;
if (nmax>SHRT_MAX) n = SHRT_MAX;
else if (nmax<0) n = 0;
for (u2=r->units;u2;u2=u2->next) {
if (u2->faction!=u->faction && getguard(u2)&GUARD_TAX
&& cansee(u2->faction, u->region, u, 0)
&& !alliedunit(u, u2->faction, HELP_GUARD)) {
cmistake(u, ord, 70, MSG_EVENT);
return;
}
}
if (d==NODIRECTION) {
cmistake(u, ord, 71, MSG_PRODUCE);
} else {
short road = rroad(r, d);
n = min(n, road);
if (n!=0) {
region * r2 = rconnect(r,d);
int willdo = eff_skill(u, SK_ROAD_BUILDING, r)*u->number;
willdo = min(willdo, n);
if (willdo==0) {
/* TODO: error message */
}
if (willdo>SHRT_MAX) road = 0;
else road = road - (short)willdo;
rsetroad(r, d, road);
ADDMSG(&u->faction->msgs, msg_message("destroy_road",
"unit from to", u, r, r2));
}
}
}
int
destroy_cmd(unit * u, struct order * ord)
{
ship *sh;
unit *u2;
region * r = u->region;
#if 0
const construction * con = NULL;
int size = 0;
#endif
const char *s;
int n = INT_MAX;
if (u->number < 1)
return 0;
init_tokens(ord);
skip_token();
s = getstrtoken();
if (findparam(s, u->faction->locale)==P_ROAD) {
destroy_road(u, INT_MAX, ord);
return 0;
}
if (s && *s) {
n = atoi(s);
if(n <= 0) {
cmistake(u, ord, 288, MSG_PRODUCE);
return 0;
}
}
if (getparam(u->faction->locale) == P_ROAD) {
destroy_road(u, n, ord);
return 0;
}
if (!fval(u, UFL_OWNER)) {
cmistake(u, ord, 138, MSG_PRODUCE);
return 0;
}
if (u->building) {
building *b = u->building;
if (a_find(b->attribs, &at_nodestroy)) {
cmistake(u, ord, 14, MSG_EVENT);
return 0;
}
#if 0
con = b->type->construction;
size = b->size;
#endif
if(n >= b->size) {
/* destroy completly */
/* all units leave the building */
for (u2 = r->units; u2; u2 = u2->next)
if (u2->building == b) {
u2->building = 0;
freset(u2, UFL_OWNER);
}
ADDMSG(&u->faction->msgs, msg_message("destroy",
"building unit", b, u));
destroy_building(b);
} else {
/* partial destroy */
b->size -= n;
ADDMSG(&u->faction->msgs, msg_message("destroy_partial",
"building unit", b, u));
}
} else if (u->ship) {
sh = u->ship;
if (a_find(sh->attribs, &at_nodestroy)) {
cmistake(u, ord, 14, MSG_EVENT);
return 0;
}
#if 0
con = sh->type->construction;
size = (sh->size * DAMAGE_SCALE - sh->damage) / DAMAGE_SCALE;
#endif
if (rterrain(r) == T_OCEAN) {
cmistake(u, ord, 14, MSG_EVENT);
return 0;
}
if(n >= (sh->size*100)/sh->type->construction->maxsize) {
/* destroy completly */
/* all units leave the ship */
for (u2 = r->units; u2; u2 = u2->next)
if (u2->ship == sh) {
u2->ship = 0;
freset(u2, UFL_OWNER);
}
ADDMSG(&u->faction->msgs, msg_message("shipdestroy",
"unit region ship", u, r, sh));
destroy_ship(sh);
} else {
/* partial destroy */
sh->size -= (sh->type->construction->maxsize * n)/100;
ADDMSG(&u->faction->msgs, msg_message("shipdestroy_partial",
"unit region ship", u, r, sh));
}
} else {
log_error(("Die Einheit %s von %s war owner eines objects, war aber weder in einer Burg noch in einem Schiff.\n",
unitname(u), u->faction->name, u->faction->email));
}
#if 0
/* Achtung: Nicht an ZERSTÖRE mit Punktangabe angepaßt! */
if (con) {
/* TODO: ZERSTÖRE - Man sollte alle Materialien zurückkriegen können: */
int c;
for (c=0;con->materials[c].number;++c) {
const requirement * rq = con->materials+c;
int recycle = (int)(rq->recycle * rq->number * size/con->reqsize);
if (recycle)
change_resource(u, rq->type, recycle);
}
}
#endif
return 0;
}
/* ------------------------------------------------------------- */
void
build_road(region * r, unit * u, int size, direction_t d)
{
int n, left;
if (!eff_skill(u, SK_ROAD_BUILDING, r)) {
cmistake(u, u->thisorder, 103, MSG_PRODUCE);
return;
}
if (besieged(u)) {
cmistake(u, u->thisorder, 60, MSG_PRODUCE);
return;
}
if (terrain[rterrain(r)].roadreq < 0) {
cmistake(u, u->thisorder, 94, MSG_PRODUCE);
return;
}
if (rterrain(r) == T_SWAMP) {
/* wenn kein Damm existiert */
static const struct building_type * bt_dam;
if (!bt_dam) bt_dam = bt_find("dam");
assert(bt_dam);
if (!buildingtype_exists(r, bt_dam)) {
cmistake(u, u->thisorder, 132, MSG_PRODUCE);
return;
}
}
if (rterrain(r) == T_DESERT) {
static const struct building_type * bt_caravan;
if (!bt_caravan) bt_caravan = bt_find("caravan");
assert(bt_caravan);
/* wenn keine Karawanserei existiert */
if (!buildingtype_exists(r, bt_caravan)) {
cmistake(u, u->thisorder, 133, MSG_PRODUCE);
return;
}
}
if (rterrain(r) == T_GLACIER) {
static const struct building_type * bt_tunnel;
if (!bt_tunnel) bt_tunnel = bt_find("tunnel");
assert(bt_tunnel);
/* wenn kein Tunnel existiert */
if (!buildingtype_exists(r, bt_tunnel)) {
cmistake(u, u->thisorder, 131, MSG_PRODUCE);
return;
}
}
if (!get_pooled(u, r, R_STONE) && u->race != new_race[RC_STONEGOLEM]) {
cmistake(u, u->thisorder, 151, MSG_PRODUCE);
return;
}
/* left kann man noch bauen */
left = terrain[rterrain(r)].roadreq - rroad(r, d);
/* hoffentlich ist r->road <= terrain[rterrain(r)].roadreq, n also >= 0 */
if (left <= 0) {
sprintf(buf, "In %s gibt es keine Brücken und Straßen "
"mehr zu bauen", regionname(r, u->faction));
mistake(u, u->thisorder, buf, MSG_PRODUCE);
return;
}
/* baumaximum anhand der rohstoffe */
if (u->race == new_race[RC_STONEGOLEM]){
n = u->number * GOLEM_STONE;
} else {
n = get_pooled(u, r, R_STONE);
}
left = min(n, left);
if (size>0) left = min(size, left);
/* n = maximum by skill. try to maximize it */
n = u->number * eff_skill(u, SK_ROAD_BUILDING, r);
if (n < left) {
item * itm = *i_find(&u->items, olditemtype[I_RING_OF_NIMBLEFINGER]);
if (itm!=NULL && itm->number>0) {
int rings = min(u->number, itm->number);
n = n * (9*rings+u->number) / u->number;
}
}
if (n < left) {
int dm = get_effect(u, oldpotiontype[P_DOMORE]);
if (dm != 0) {
int sk = eff_skill(u, SK_ROAD_BUILDING, r);
dm = (left - n + sk - 1) / sk;
dm = min(dm, u->number);
change_effect(u, oldpotiontype[P_DOMORE], -dm);
n += dm * sk;
} /* Auswirkung Schaffenstrunk */
}
/* make minimum of possible and available: */
n = min(left, n);
/* n is now modified by several special effects, so we have to
* minimize it again to make sure the road will not grow beyond
* maximum. */
rsetroad(r, d, rroad(r, d) + (short)n);
if (u->race == new_race[RC_STONEGOLEM]) {
int golemsused = n / GOLEM_STONE;
if (n%GOLEM_STONE != 0){
++golemsused;
}
scale_number(u, u->number - golemsused);
} else {
use_pooled(u, r, R_STONE, n);
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
produceexp(u, SK_ROAD_BUILDING, min(n, u->number));
}
ADDMSG(&u->faction->msgs, msg_message("buildroad",
"region unit size", r, u, n));
}
/* ------------------------------------------------------------- */
/* ** ** ** ** ** ** *
* new build rules *
* ** ** ** ** ** ** */
static int
required(int size, int msize, int maxneed)
/* um size von msize Punkten zu bauen,
* braucht man required von maxneed resourcen */
{
int used;
used = size * maxneed / msize;
if (size * maxneed % msize)
++used;
return used;
}
static int
matmod(const attrib * a, const unit * u, const resource_type * material, int value)
{
for (a=a_find((attrib*)a, &at_matmod);a;a=a->nexttype) {
mm_fun fun = (mm_fun)a->data.f;
value = fun(u, material, value);
if (value<0) return value; /* pass errors to caller */
}
return value;
}
/** Use up resources for building an object.
* Build up to 'size' points of 'type', where 'completed'
* of the first object have already been finished. return the
* actual size that could be built.
*/
int
build(unit * u, const construction * ctype, int completed, int want)
{
const construction * type = ctype;
int skills; /* number of skill points remainig */
int dm = get_effect(u, oldpotiontype[P_DOMORE]);
int made = 0;
int basesk, effsk;
if (want<=0) return 0;
if (type==NULL) return 0;
if (type->improvement==NULL && completed==type->maxsize)
return ECOMPLETE;
basesk = effskill(u, type->skill);
if (basesk==0) return ENEEDSKILL;
effsk = basesk;
if (inside_building(u)) {
effsk = skillmod(u->building->type->attribs, u, u->region, type->skill, effsk, SMF_PRODUCTION);
}
effsk = skillmod(type->attribs, u, u->region, type->skill, effsk, SMF_PRODUCTION);
if (effsk<0) return effsk; /* pass errors to caller */
if (effsk==0) return ENEEDSKILL;
skills = effsk * u->number;
/* technically, nimblefinge and domore should be in a global set of "game"-attributes,
* (as at_skillmod) but for a while, we're leaving them in here. */
if (dm != 0) {
/* Auswirkung Schaffenstrunk */
dm = min(dm, u->number);
change_effect(u, oldpotiontype[P_DOMORE], -dm);
skills += dm * effsk;
}
for (;want>0 && skills>0;) {
int c, n;
/* skip over everything that's already been done:
* type->improvement==NULL means no more improvements, but no size limits
* type->improvement==type means build another object of the same time while material lasts
* type->improvement==x means build x when type is finished
*/
while (type->improvement!=NULL &&
type->improvement!=type &&
type->maxsize>0 &&
type->maxsize<=completed)
{
completed -= type->maxsize;
type = type->improvement;
}
if (type==NULL) {
if (made==0) return ECOMPLETE;
break; /* completed */
}
/* Hier ist entweder maxsize == -1, oder completed < maxsize.
* Andernfalls ist das Datenfile oder sonstwas kaputt...
* (enno): Nein, das ist für Dinge, bei denen die nächste Ausbaustufe
* die gleiche wie die vorherige ist. z.b. gegenstände.
*/
if (type->maxsize>1) {
completed = completed % type->maxsize;
}
else {
completed = 0; assert(type->reqsize>=1);
}
if (basesk < type->minskill) {
if (made==0) return ELOWSKILL; /* not good enough to go on */
}
/* n = maximum buildable size */
if (type->minskill > 1) {
n = skills / type->minskill;
} else {
n = skills;
}
/* Flinkfingerring wirkt nicht auf Mengenbegrenzte (magische)
* Talente */
if (max_skill(u->faction, type->skill)==INT_MAX) {
int i = 0;
item * itm = *i_find(&u->items, olditemtype[I_RING_OF_NIMBLEFINGER]);
if (itm!=NULL) i = itm->number;
if (i>0) {
int rings = min(u->number, i);
n = n * (9*rings+u->number) / u->number;
}
}
if (want>0) {
n = min(want, n);
}
if (type->maxsize>0) {
n = min(type->maxsize-completed, n);
if (type->improvement==NULL) {
want = n;
}
}
if (type->materials) for (c=0;n>0 && type->materials[c].number;c++) {
resource_t rtype = type->materials[c].type;
int need;
int have = get_pooled(u, NULL, rtype);
int prebuilt;
int canuse = have;
if (inside_building(u)) {
canuse = matmod(u->building->type->attribs, u, oldresourcetype[rtype], canuse);
#if 0
/* exploit-check */
} else if (u->building) {
int abuse = matmod(u->building->type->attribs, u, oldresourcetype[rtype], canuse);
if (abuse>canuse) {
log_printf("ABUSE: %s saves %u %s through exploit\n",
itoa36(u->faction->no), abuse-canuse,
oldresourcetype[rtype]->_name[0]);
}
#endif
}
if (canuse<0) return canuse; /* pass errors to caller */
canuse = matmod(type->attribs, u, oldresourcetype[rtype], canuse);
if (type->reqsize>1) {
prebuilt = required(completed, type->reqsize, type->materials[c].number);
for (;n;) {
need = required(completed + n, type->reqsize, type->materials[c].number);
if (need-prebuilt<=canuse) break;
--n; /* TODO: optimieren? */
}
} else {
int maxn = canuse / type->materials[c].number;
if (maxn < n) n = maxn;
}
}
if (n<=0) {
if (made==0) return ENOMATERIALS;
else break;
}
if (type->materials) for (c=0;type->materials[c].number;c++) {
resource_t rtype = type->materials[c].type;
int prebuilt = required(completed, type->reqsize, type->materials[c].number);
int need = required(completed + n, type->reqsize, type->materials[c].number);
int multi = 1;
int canuse = 100; /* normalization */
if (inside_building(u)) canuse = matmod(u->building->type->attribs, u, oldresourcetype[rtype], canuse);
if (canuse<0) return canuse; /* pass errors to caller */
canuse = matmod(type->attribs, u, oldresourcetype[rtype], canuse);
assert(canuse % 100 == 0 || !"only constant multipliers are implemented in build()");
multi = canuse/100;
if (canuse<0) return canuse; /* pass errors to caller */
use_pooled(u, NULL, rtype, (need-prebuilt+multi-1)/multi);
}
made += n;
skills -= n * type->minskill;
want -= n;
completed = completed + n;
}
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
produceexp(u, ctype->skill, min(made, u->number));
return made;
}
int
maxbuild(const unit * u, const construction * cons)
/* calculate maximum size that can be built from available material */
/* !! ignores maximum objectsize and improvements...*/
{
int c;
int maximum = INT_MAX;
for (c=0;cons->materials[c].number;c++) {
resource_t rtype = cons->materials[c].type;
int have = get_pooled(u, NULL, rtype);
int need = required(1, cons->reqsize, cons->materials[c].number);
if (have<need) {
cmistake(u, u->thisorder, 88, MSG_PRODUCE);
return 0;
}
else maximum = min(maximum, have/need);
}
return maximum;
}
/** old build routines */
void
build_building(unit * u, const building_type * btype, int want, order * ord)
{
region * r = u->region;
boolean newbuilding = false;
int c, built = 0, id;
building * b = NULL;
/* einmalige Korrektur */
static char buffer[8 + IDSIZE + 1 + NAMESIZE + 1];
const char * btname;
order * new_order = NULL;
const struct locale * lang = u->faction->locale;
if (eff_skill(u, SK_BUILDING, r) == 0) {
cmistake(u, ord, 101, MSG_PRODUCE);
return;
}
/* Falls eine Nummer angegeben worden ist, und ein Gebaeude mit der
* betreffenden Nummer existiert, ist b nun gueltig. Wenn keine Burg
* gefunden wurde, dann wird nicht einfach eine neue erbaut. Ansonsten
* baut man an der eigenen burg weiter. */
/* Wenn die angegebene Nummer falsch ist, KEINE Burg bauen! */
id = atoi36(getstrtoken());
if (id!=0){ /* eine Nummer angegeben, keine neue Burg bauen */
b = findbuilding(id);
if (!b || b->region != u->region){ /* eine Burg mit dieser Nummer gibt es hier nicht */
/* vieleicht Tippfehler und die eigene Burg ist gemeint? */
if (u->building && u->building->type==btype) {
b = u->building;
} else {
/* keine neue Burg anfangen wenn eine Nummer angegeben war */
cmistake(u, ord, 6, MSG_PRODUCE);
return;
}
}
}
if (b) btype = b->type;
if (b && fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype)) {
/* only one of these per region */
cmistake(u, ord, 93, MSG_PRODUCE);
return;
}
if (besieged(u)) {
/* units under siege can not build */
cmistake(u, ord, 60, MSG_PRODUCE);
return;
}
if (btype->flags & BTF_NOBUILD) {
/* special building, cannot be built */
cmistake(u, ord, 221, MSG_PRODUCE);
return;
}
if (b) built = b->size;
if (want<=0 || want == INT_MAX) {
if(b == NULL) {
if(btype->maxsize > 0) {
want = btype->maxsize - built;
} else {
want = INT_MAX;
}
} else {
if(b->type->maxsize > 0) {
want = b->type->maxsize - built;
} else {
want = INT_MAX;
}
}
}
built = build(u, btype->construction, built, want);
switch (built) {
case ECOMPLETE:
/* the building is already complete */
cmistake(u, ord, 4, MSG_PRODUCE);
return;
case ENOMATERIALS: {
/* something missing from the list of materials */
const construction * cons = btype->construction;
char * ch = buf;
assert(cons);
for (c=0;cons->materials[c].number; c++) {
int n;
if (c!=0) strcat(ch++, ",");
n = cons->materials[c].number / cons->reqsize;
sprintf(ch, " %d %s", n?n:1,
LOC(lang, resname(cons->materials[c].type, cons->materials[c].number!=1)));
ch = ch+strlen(ch);
}
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "build_required",
"required", buf));
return;
}
case ELOWSKILL:
case ENEEDSKILL:
/* no skill, or not enough skill points to build */
cmistake(u, ord, 50, MSG_PRODUCE);
return;
}
/* at this point, the building size is increased. */
if (b==NULL) {
/* build a new building */
b = new_building(btype, r, lang);
b->type = btype;
fset(b, BLD_MAINTAINED);
/* Die Einheit befindet sich automatisch im Inneren der neuen Burg. */
leave(r, u);
u->building = b;
fset(u, UFL_OWNER);
newbuilding = true;
}
btname = LOC(lang, btype->_name);
if (want-built <= 0) {
/* gebäude fertig */
strcpy(buffer, LOC(lang, "defaultorder"));
new_order = parse_order(buffer, lang);
} else if (want!=INT_MAX) {
/* reduzierte restgröße */
sprintf(buffer, "%s %d %s %s", LOC(lang, keywords[K_MAKE]), want-built, btname, buildingid(b));
new_order = parse_order(buffer, lang);
} else if (btname) {
/* Neues Haus, Befehl mit Gebäudename */
sprintf(buffer, "%s %s %s", LOC(lang, keywords[K_MAKE]), btname, buildingid(b));
new_order = parse_order(buffer, u->faction->locale);
}
if (new_order) {
#ifdef LASTORDER
set_order(&u->lastorder, new_order);
#else
replace_order(&u->orders, ord, new_order);
free_order(new_order);
#endif
}
b->size += built;
update_lighthouse(b);
ADDMSG(&u->faction->msgs, msg_message("buildbuilding",
"building unit size", b, u, built));
}
static void
build_ship(unit * u, ship * sh, int want)
{
const construction * construction = sh->type->construction;
int size = (sh->size * DAMAGE_SCALE - sh->damage) / DAMAGE_SCALE;
int n;
#if 0
int can = u->number * effskill(u, SK_SHIPBUILDING) / construction->minskill;
if (want > 0) can = min(want, can);
can = min(can, construction->maxsize+sh->damage); /* 100% bauen + 100% reparieren */
#endif
int can = build(u, construction, size, want);
if ((n=construction->maxsize - sh->size)>0 && can>0) {
if (can>=n) {
sh->size += n;
can -= n;
}
else {
sh->size += can;
n=can;
can = 0;
}
}
if (sh->damage && can) {
int repair = min(sh->damage, can * DAMAGE_SCALE);
n += repair / DAMAGE_SCALE;
if (repair % DAMAGE_SCALE) ++n;
sh->damage = sh->damage - repair;
}
if (n) ADDMSG(&u->faction->msgs,
msg_message("buildship", "ship unit size", sh, u, n));
}
void
create_ship(region * r, unit * u, const struct ship_type * newtype, int want, order * ord)
{
static char buffer[IDSIZE + 2 * KEYWORDSIZE + 3];
ship *sh;
int msize;
const construction * cons = newtype->construction;
order * new_order;
if (!eff_skill(u, SK_SHIPBUILDING, r)) {
cmistake(u, ord, 100, MSG_PRODUCE);
return;
}
if (besieged(u)) {
cmistake(u, ord, 60, MSG_PRODUCE);
return;
}
/* check if skill and material for 1 size is available */
if (eff_skill(u, cons->skill, r) < cons->minskill) {
sprintf(buf, "Um %s zu bauen, braucht man ein Talent von "
"mindestens %d.", newtype->name[1], cons->minskill);
mistake(u, ord, buf, MSG_PRODUCE);
return;
}
msize = maxbuild(u, cons);
if (msize==0) {
cmistake(u, ord, 88, MSG_PRODUCE);
return;
}
if (want>0) want = min(want, msize);
else want = msize;
sh = new_ship(newtype, u->faction->locale, r);
leave(r, u);
u->ship = sh;
fset(u, UFL_OWNER);
sprintf(buffer, "%s %s %s",
locale_string(u->faction->locale, keywords[K_MAKE]), locale_string(u->faction->locale, parameters[P_SHIP]), shipid(sh));
new_order = parse_order(buffer, u->faction->locale);
#ifdef LASTORDER
set_order(&u->lastorder, new_order);
#else
replace_order(&u->orders, ord, new_order);
free_order(new_order);
#endif
build_ship(u, sh, want);
}
void
continue_ship(region * r, unit * u, int want)
{
const construction * cons;
ship *sh;
int msize;
if (!eff_skill(u, SK_SHIPBUILDING, r)) {
cmistake(u, u->thisorder, 100, MSG_PRODUCE);
return;
}
/* Die Schiffsnummer bzw der Schiffstyp wird eingelesen */
sh = getship(r);
if (!sh) sh = u->ship;
if (!sh) {
cmistake(u, u->thisorder, 20, MSG_PRODUCE);
return;
}
cons = sh->type->construction;
assert(cons->improvement==NULL); /* sonst ist construction::size nicht ship_type::maxsize */
if (sh->size==cons->maxsize && !sh->damage) {
cmistake(u, u->thisorder, 16, MSG_PRODUCE);
return;
}
if (eff_skill(u, cons->skill, r) < cons->minskill) {
sprintf(buf, "Um %s zu bauen, braucht man ein Talent von "
"mindestens %d.", sh->type->name[1], cons->minskill);
mistake(u, u->thisorder, buf, MSG_PRODUCE);
return;
}
msize = maxbuild(u, cons);
if (msize==0) {
cmistake(u, u->thisorder, 88, MSG_PRODUCE);
return;
}
if (want > 0) want = min(want, msize);
else want = msize;
build_ship(u, sh, want);
}
/* ------------------------------------------------------------- */
static boolean
mayenter(region * r, unit * u, building * b)
{
unit *u2;
if (fval(b, BLD_UNGUARDED)) return true;
u2 = buildingowner(r, b);
if (u2==NULL || ucontact(u2, u)
|| alliedunit(u2, u->faction, HELP_GUARD)) return true;
return false;
}
static int
mayboard(const unit * u, const ship * sh)
{
unit *u2 = shipowner(sh);
return (!u2 || ucontact(u2, u) || alliedunit(u2, u->faction, HELP_GUARD));
}
void
remove_contacts(void)
{
region *r;
unit *u;
attrib *a;
for (r = regions; r; r = r->next) {
for (u = r->units; u; u = u->next) {
a = (attrib *)a_find(u->attribs, &at_contact);
while(a != NULL) {
attrib * ar = a;
a = a->nexttype;
a_remove(&u->attribs, ar);
}
}
}
}
int
leave_cmd(unit * u, struct order * ord)
{
region * r = u->region;
if (r->terrain == T_OCEAN && u->ship) {
if(!fval(u->race, RCF_SWIM)) {
cmistake(u, ord, 11, MSG_MOVE);
return 0;
}
if(get_item(u, I_HORSE)) {
cmistake(u, ord, 231, MSG_MOVE);
return 0;
}
}
if (!slipthru(r, u, u->building)) {
sprintf(buf, "%s wird belagert.", buildingname(u->building));
mistake(u, ord, buf, MSG_MOVE);
} else {
leave(r, u);
}
return 0;
}
static boolean
entership(unit * u, ship * sh, struct order * ord, boolean lasttry)
{
/* Muß abgefangen werden, sonst könnten Schwimmer an
* Bord von Schiffen an Land gelangen. */
if( !fval(u->race, RCF_WALK) &&
!fval(u->race, RCF_FLY)) {
cmistake(u, ord, 233, MSG_MOVE);
return false;
}
if (!sh) {
if (lasttry) cmistake(u, ord, 20, MSG_MOVE);
return false;
}
if (sh==u->ship) return true;
if (!mayboard(u, sh)) {
if (lasttry) cmistake(u, ord, 34, MSG_MOVE);
return false;
}
if (CheckOverload()) {
int sweight, scabins;
int mweight = shipcapacity(sh);
int mcabins = sh->type->cabins;
if (mweight>0 && mcabins>0) {
getshipweight(sh, &sweight, &scabins);
sweight += weight(u);
scabins += u->number;
sweight = ((sweight+99) / 100) * 100; /* Silberreste aufrunden */
if (sweight > mweight || scabins > mcabins) {
if (lasttry) cmistake(u, ord, 34, MSG_MOVE);
return false;
}
}
}
leave(u->region, u);
u->ship = sh;
if (shipowner(sh) == 0) {
fset(u, UFL_OWNER);
}
return true;
}
void
do_misc(boolean lasttry)
{
region *r;
ship *sh;
building *b;
/* lasttry: Fehler nur im zweiten Versuch melden. Sonst konfus. */
for (r = regions; r; r = r->next) {
unit *u;
for (u = r->units; u; u = u->next) {
order * ord;
for (ord = u->orders; ord; ord = ord->next) {
switch (get_keyword(ord)) {
case K_CONTACT:
contact_cmd(u, ord, lasttry);
break;
}
}
}
for (u = r->units; u; u = u->next) {
order ** ordp = &u->orders;
while (*ordp) {
order * ord = *ordp;
if (get_keyword(ord) == K_ENTER) {
init_tokens(ord);
skip_token();
switch (getparam(u->faction->locale)) {
case P_BUILDING:
case P_GEBAEUDE:
/* Schwimmer können keine Gebäude betreten, außer diese sind
* auf dem Ozean */
if( !fval(u->race, RCF_WALK) && !fval(u->race, RCF_FLY)) {
if (rterrain(r) != T_OCEAN){
if (lasttry) cmistake(u, ord, 232, MSG_MOVE);
break;
}
}
b = getbuilding(r);
if (!b) {
if(lasttry) cmistake(u, ord, 6, MSG_MOVE);
break;
}
/* Gebäude auf dem Ozean sollte man betreten dürfen
if(rterrain(r) == T_OCEAN) {
if (lasttry) cmistake(u, ord, 297, MSG_MOVE);
break;
}
*/
if (!mayenter(r, u, b)) {
if(lasttry) {
sprintf(buf, "Der Eintritt in %s wurde verwehrt",
buildingname(b));
mistake(u, ord, buf, MSG_MOVE);
}
break;
}
if (!slipthru(r, u, b)) {
if(lasttry) {
sprintf(buf, "%s wird belagert", buildingname(b));
mistake(u, ord, buf, MSG_MOVE);
}
break;
}
/* Wenn wir hier angekommen sind, war der Befehl
* erfolgreich und wir löschen ihn, damit er im
* zweiten Versuch nicht nochmal ausgeführt wird. */
*ordp = ord->next;
ord->next = NULL;
free_order(ord);
leave(r, u);
u->building = b;
if (buildingowner(r, b) == 0) {
fset(u, UFL_OWNER);
}
break;
case P_SHIP:
sh = getship(r);
entership(u, sh, ord, lasttry);
break;
default:
if (lasttry) cmistake(u, ord, 79, MSG_MOVE);
}
}
if (*ordp==ord) ordp = &ord->next;
}
}
}
}