forked from github/server
1047 lines
30 KiB
C
1047 lines
30 KiB
C
/*
|
|
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
|
|
Katja Zedel <katze@felidae.kn-bremen.de
|
|
Christian Schlittchen <corwin@amber.kn-bremen.de>
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
**/
|
|
|
|
#ifdef _MSC_VER
|
|
#include <platform.h>
|
|
#endif
|
|
#include "build.h"
|
|
|
|
#include "alchemy.h"
|
|
#include "direction.h"
|
|
#include "move.h"
|
|
#include "study.h"
|
|
#include "guard.h"
|
|
#include "laws.h"
|
|
#include "skill.h"
|
|
#include "lighthouse.h"
|
|
|
|
/* kernel includes */
|
|
#include <kernel/ally.h>
|
|
#include <kernel/alliance.h>
|
|
#include <kernel/attrib.h>
|
|
#include <kernel/building.h>
|
|
#include <kernel/config.h>
|
|
#include <kernel/connection.h>
|
|
#include <kernel/curse.h>
|
|
#include <kernel/event.h>
|
|
#include <kernel/faction.h>
|
|
#include <kernel/group.h>
|
|
#include <kernel/item.h>
|
|
#include <kernel/messages.h>
|
|
#include <kernel/order.h>
|
|
#include <kernel/pool.h>
|
|
#include <kernel/race.h>
|
|
#include <kernel/region.h>
|
|
#include <kernel/resources.h>
|
|
#include <kernel/ship.h>
|
|
#include <kernel/terrain.h>
|
|
#include <kernel/terrainid.h>
|
|
#include <kernel/unit.h>
|
|
|
|
/* from libutil */
|
|
#include <util/goodies.h>
|
|
#include <util/language.h>
|
|
#include <util/log.h>
|
|
#include <util/param.h>
|
|
#include <util/parser.h>
|
|
#include <util/resolve.h>
|
|
#include <util/strings.h>
|
|
|
|
/* from libc */
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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(getid());
|
|
for (sh = r->ships; sh; sh = sh->next) {
|
|
if (sh == sx)
|
|
return sh;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- */
|
|
|
|
/* ------------------------------------------------------------- */
|
|
|
|
static void destroy_road(unit * u, int nmax, struct order *ord)
|
|
{
|
|
char token[128];
|
|
const char *s = gettoken(token, sizeof(token));
|
|
direction_t d = s ? get_direction(s, u->faction->locale) : NODIRECTION;
|
|
if (d == NODIRECTION) {
|
|
/* Die Richtung wurde nicht erkannt */
|
|
cmistake(u, ord, 71, MSG_PRODUCE);
|
|
}
|
|
else {
|
|
unit *u2;
|
|
region *r = u->region;
|
|
int road, n = 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 && is_guard(u2)
|
|
&& cansee(u2->faction, u->region, u, 0)
|
|
&& !alliedunit(u, u2->faction, HELP_GUARD)) {
|
|
cmistake(u, ord, 70, MSG_EVENT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
road = rroad(r, d);
|
|
if (n > road) n = road;
|
|
|
|
if (n != 0) {
|
|
region *r2 = rconnect(r, d);
|
|
int willdo = effskill(u, SK_ROAD_BUILDING, NULL) * u->number;
|
|
if (willdo > n) willdo = n;
|
|
if (willdo == 0) {
|
|
/* TODO: error message */
|
|
}
|
|
else if (willdo > SHRT_MAX)
|
|
road = 0;
|
|
else
|
|
road = (short)(road - willdo);
|
|
rsetroad(r, d, road);
|
|
if (willdo > 0) {
|
|
ADDMSG(&u->faction->msgs, msg_message("destroy_road",
|
|
"unit from to", u, r, r2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int recycle(unit *u, construction *con, int size) {
|
|
/* TODO: Nicht an ZERSTOERE mit Punktangabe angepasst! */
|
|
int c;
|
|
for (c = 0; con->materials[c].number; ++c) {
|
|
const requirement *rq = con->materials + c;
|
|
int num = (rq->number * size / con->reqsize) / 2;
|
|
if (num) {
|
|
change_resource(u, rq->rtype, num);
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
int destroy_cmd(unit * u, struct order *ord)
|
|
{
|
|
char token[128];
|
|
ship *sh;
|
|
unit *u2;
|
|
region *r = u->region;
|
|
int size = 0;
|
|
const char *s;
|
|
int n = INT_MAX;
|
|
|
|
if (u->number < 1)
|
|
return 1;
|
|
|
|
if (fval(u, UFL_LONGACTION)) {
|
|
cmistake(u, ord, 52, MSG_PRODUCE);
|
|
return 52;
|
|
}
|
|
|
|
init_order_depr(ord);
|
|
s = gettoken(token, sizeof(token));
|
|
|
|
if (s && *s) {
|
|
ERRNO_CHECK();
|
|
n = atoi(s);
|
|
errno = 0;
|
|
if (n <= 0) {
|
|
n = INT_MAX;
|
|
}
|
|
else {
|
|
s = gettoken(token, sizeof(token));
|
|
}
|
|
}
|
|
|
|
if (s && isparam(s, u->faction->locale, P_ROAD)) {
|
|
destroy_road(u, n, ord);
|
|
return 0;
|
|
}
|
|
|
|
if (u->building) {
|
|
building *b = u->building;
|
|
|
|
if (u != building_owner(b)) {
|
|
cmistake(u, ord, 138, MSG_PRODUCE);
|
|
return 138;
|
|
}
|
|
if (fval(b->type, BTF_INDESTRUCTIBLE)) {
|
|
cmistake(u, ord, 138, MSG_PRODUCE);
|
|
return 138;
|
|
}
|
|
if (n >= b->size) {
|
|
building_stage *stage;
|
|
/* destroy completly */
|
|
/* all units leave the building */
|
|
for (u2 = r->units; u2; u2 = u2->next) {
|
|
if (u2->building == b) {
|
|
leave_building(u2);
|
|
}
|
|
}
|
|
ADDMSG(&u->faction->msgs, msg_message("destroy", "building unit", b, u));
|
|
for (stage = b->type->stages; stage; stage = stage->next) {
|
|
size = recycle(u, stage->construction, size);
|
|
}
|
|
remove_building(&r->buildings, b);
|
|
}
|
|
else {
|
|
/* TODO: partial destroy does not recycle */
|
|
b->size -= n;
|
|
ADDMSG(&u->faction->msgs, msg_message("destroy_partial",
|
|
"building unit", b, u));
|
|
}
|
|
}
|
|
else if (u->ship) {
|
|
sh = u->ship;
|
|
|
|
if (u != ship_owner(sh)) {
|
|
cmistake(u, ord, 138, MSG_PRODUCE);
|
|
return 138;
|
|
}
|
|
if (fval(r->terrain, SEA_REGION)) {
|
|
cmistake(u, ord, 14, MSG_EVENT);
|
|
return 14;
|
|
}
|
|
|
|
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) {
|
|
leave_ship(u2);
|
|
}
|
|
}
|
|
ADDMSG(&u->faction->msgs, msg_message("shipdestroy",
|
|
"unit region ship", u, r, sh));
|
|
size = recycle(u, sh->type->construction, size);
|
|
remove_ship(&sh->region->ships, 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 {
|
|
cmistake(u, ord, 138, MSG_PRODUCE);
|
|
return 138;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- */
|
|
|
|
void build_road(unit * u, int size, direction_t d)
|
|
{
|
|
region *r = u->region;
|
|
int n, left, effsk;
|
|
region *rn = rconnect(r, d);
|
|
|
|
assert(u->number);
|
|
effsk = effskill(u, SK_ROAD_BUILDING, NULL);
|
|
if (!effsk) {
|
|
cmistake(u, u->thisorder, 103, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
|
|
if (rn == NULL || rn->terrain->max_road < 0) {
|
|
cmistake(u, u->thisorder, 94, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
|
|
if (r->terrain->max_road < 0) {
|
|
cmistake(u, u->thisorder, 94, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
|
|
if (r->terrain == newterrain(T_SWAMP)) {
|
|
/* wenn kein Damm existiert */
|
|
const struct building_type *bt_dam = bt_find("dam");
|
|
if (!bt_dam || !buildingtype_exists(r, bt_dam, true)) {
|
|
cmistake(u, u->thisorder, 132, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
}
|
|
else if (r->terrain == newterrain(T_DESERT)) {
|
|
const struct building_type *bt_caravan = bt_find("caravan");
|
|
/* wenn keine Karawanserei existiert */
|
|
if (!bt_caravan || !buildingtype_exists(r, bt_caravan, true)) {
|
|
cmistake(u, u->thisorder, 133, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
}
|
|
else if (r->terrain == newterrain(T_GLACIER)) {
|
|
const struct building_type *bt_tunnel = bt_find("tunnel");
|
|
/* wenn kein Tunnel existiert */
|
|
if (!bt_tunnel || !buildingtype_exists(r, bt_tunnel, true)) {
|
|
cmistake(u, u->thisorder, 131, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* left kann man noch bauen */
|
|
left = r->terrain->max_road - rroad(r, d);
|
|
|
|
/* hoffentlich ist r->road <= r->terrain->max_road, n also >= 0 */
|
|
if (left <= 0) {
|
|
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
|
|
"error_roads_finished", ""));
|
|
return;
|
|
}
|
|
|
|
if (size > 0 && left > size) {
|
|
left = size;
|
|
}
|
|
/* baumaximum anhand der rohstoffe */
|
|
if (u_race(u) == get_race(RC_STONEGOLEM)) {
|
|
n = u->number * GOLEM_STONE;
|
|
}
|
|
else {
|
|
n = get_pooled(u, get_resourcetype(R_STONE), GET_DEFAULT, left);
|
|
if (n == 0) {
|
|
cmistake(u, u->thisorder, 151, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
}
|
|
if (n < left) left = n;
|
|
|
|
/* n = maximum by skill. try to maximize it */
|
|
n = u->number * effsk;
|
|
if (n < left) {
|
|
const resource_type *ring = get_resourcetype(R_RING_OF_NIMBLEFINGER);
|
|
item *itm = ring ? *i_find(&u->items, ring->itype) : 0;
|
|
if (itm != NULL && itm->number > 0) {
|
|
int rings = (u->number < itm->number) ? u->number : itm->number;
|
|
n = n * ((roqf_factor() - 1) * rings + u->number) / u->number;
|
|
}
|
|
}
|
|
if (n < left) {
|
|
int dm = get_effect(u, oldpotiontype[P_DOMORE]);
|
|
if (dm != 0) {
|
|
int todo = (left - n + effsk - 1) / effsk;
|
|
if (todo > u->number) todo = u->number;
|
|
if (dm > todo) dm = todo;
|
|
change_effect(u, oldpotiontype[P_DOMORE], -dm);
|
|
n += dm * effsk;
|
|
} /* Auswirkung Schaffenstrunk */
|
|
}
|
|
|
|
/* make minimum of possible and available: */
|
|
if (n > left) n = left;
|
|
|
|
/* 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) + n);
|
|
|
|
if (u_race(u) == get_race(RC_STONEGOLEM)) {
|
|
int golemsused = n / GOLEM_STONE;
|
|
if (n % GOLEM_STONE != 0) {
|
|
++golemsused;
|
|
}
|
|
scale_number(u, u->number - golemsused);
|
|
}
|
|
else {
|
|
use_pooled(u, get_resourcetype(R_STONE), GET_DEFAULT, n);
|
|
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
|
|
produceexp(u, SK_ROAD_BUILDING, (n < u->number) ? 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;
|
|
assert(msize > 0);
|
|
used = size * maxneed / msize;
|
|
if (size * maxneed % msize)
|
|
++used;
|
|
return used;
|
|
}
|
|
|
|
static int matmod(const unit * u, const resource_type * rtype, int value)
|
|
{
|
|
if (rtype->modifiers) {
|
|
variant save = frac_one;
|
|
const struct building_type *btype = NULL;
|
|
const struct race *rc = u_race(u);
|
|
resource_mod *mod;
|
|
if (u->building && inside_building(u)) {
|
|
btype = u->building->type;
|
|
}
|
|
for (mod = rtype->modifiers; mod->type != RMT_END; ++mod) {
|
|
if (mod->type == RMT_USE_SAVE) {
|
|
if (!mod->btype || mod->btype == btype) {
|
|
if (!mod->race_mask || (mod->race_mask & rc->mask_item)) {
|
|
save = frac_mul(save, mod->value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return value * save.sa[0] / save.sa[1];
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int roqf_factor(void)
|
|
{
|
|
static int config;
|
|
static int value;
|
|
if (config_changed(&config)) {
|
|
value = config_get_int("rules.economy.roqf", 10);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static int use_materials(unit *u, const construction *type, int n, int completed) {
|
|
if (type->materials) {
|
|
int c;
|
|
for (c = 0; type->materials[c].number; c++) {
|
|
const struct resource_type *rtype = type->materials[c].rtype;
|
|
int prebuilt =
|
|
required(completed, type->reqsize, type->materials[c].number);
|
|
int need =
|
|
required(completed + n, type->reqsize, type->materials[c].number);
|
|
int multi, canuse = 100; /* normalization */
|
|
canuse = matmod(u, rtype, canuse);
|
|
assert(canuse >= 0);
|
|
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, rtype, GET_DEFAULT,
|
|
(need - prebuilt + multi - 1) / multi);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int count_materials(unit *u, const construction *type, int n, int completed)
|
|
{
|
|
if (type->materials) {
|
|
int c;
|
|
for (c = 0; n > 0 && type->materials[c].number; c++) {
|
|
const struct resource_type *rtype = type->materials[c].rtype;
|
|
int canuse = get_pooled(u, rtype, GET_DEFAULT, INT_MAX);
|
|
canuse = matmod(u, rtype, canuse);
|
|
|
|
assert(canuse >= 0);
|
|
if (type->reqsize > 1) {
|
|
int prebuilt = required(completed, type->reqsize,
|
|
type->materials[c].number);
|
|
while (n > 0) {
|
|
int 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int build_skill(unit *u, int basesk, int skill_mod) {
|
|
int effsk, skills;
|
|
int dm = get_effect(u, oldpotiontype[P_DOMORE]);
|
|
|
|
effsk = basesk + skill_mod;
|
|
assert(effsk >= 0);
|
|
|
|
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 */
|
|
if (dm > u->number) dm = u->number;
|
|
change_effect(u, oldpotiontype[P_DOMORE], -dm);
|
|
skills += dm * effsk;
|
|
}
|
|
return skills;
|
|
}
|
|
|
|
/** 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.
|
|
*/
|
|
static int build_limited(unit * u, const construction * con, int completed, int want, int basesk, int *skill_total) {
|
|
int skills = *skill_total;
|
|
int made = 0;
|
|
|
|
if (want <= 0) {
|
|
return 0;
|
|
}
|
|
if (con == NULL) {
|
|
return ENOMATERIALS;
|
|
}
|
|
if (completed == con->maxsize) {
|
|
return ECOMPLETE;
|
|
}
|
|
for (; want > 0 && skills > 0;) {
|
|
int err, n;
|
|
|
|
/* Hier ist entweder maxsize == -1, oder completed < maxsize.
|
|
* Andernfalls ist das Datenfile oder sonstwas kaputt...
|
|
* (enno): Nein, das ist fuer Dinge, bei denen die naechste Ausbaustufe
|
|
* die gleiche wie die vorherige ist. z.b. Gegenstaende.
|
|
*/
|
|
if (con->maxsize > 0) {
|
|
completed = completed % con->maxsize;
|
|
}
|
|
else {
|
|
completed = 0;
|
|
assert(con->reqsize >= 1);
|
|
}
|
|
|
|
if (basesk < con->minskill) {
|
|
if (made == 0)
|
|
return ELOWSKILL;
|
|
else
|
|
break; /* not good enough to go on */
|
|
}
|
|
|
|
/* n = maximum buildable size */
|
|
if (con->minskill > 1) {
|
|
n = skills / con->minskill;
|
|
}
|
|
else {
|
|
n = skills;
|
|
}
|
|
/* Flinkfingerring wirkt nicht auf Mengenbegrenzte (magische)
|
|
* Talente */
|
|
if (skill_limit(u->faction, con->skill) == INT_MAX) {
|
|
const resource_type *ring = get_resourcetype(R_RING_OF_NIMBLEFINGER);
|
|
item *itm = ring ? *i_find(&u->items, ring->itype) : 0;
|
|
int i = itm ? itm->number : 0;
|
|
if (i > 0) {
|
|
int rings = (u->number < i) ? u->number : i;
|
|
n = n * ((roqf_factor() - 1) * rings + u->number) / u->number;
|
|
}
|
|
}
|
|
|
|
if (want < n) n = want;
|
|
|
|
if (con->maxsize > 0) {
|
|
int req = con->maxsize - completed;
|
|
if (req < n) n = req;
|
|
want = n;
|
|
}
|
|
|
|
n = count_materials(u, con, n, completed);
|
|
if (n <= 0) {
|
|
if (made == 0)
|
|
return ENOMATERIALS;
|
|
else
|
|
break;
|
|
}
|
|
err = use_materials(u, con, n, completed);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
made += n;
|
|
skills -= n * con->minskill;
|
|
want -= n;
|
|
completed = completed + n;
|
|
}
|
|
*skill_total = skills;
|
|
return made;
|
|
}
|
|
|
|
int build(unit * u, const construction * con, int completed, int want, int skill_mod)
|
|
{
|
|
int skills = INT_MAX; /* number of skill points remainig */
|
|
int made, basesk = 0;
|
|
|
|
assert(con->skill != NOSKILL);
|
|
basesk = effskill(u, con->skill, NULL);
|
|
if (basesk == 0) {
|
|
return ENEEDSKILL;
|
|
}
|
|
|
|
skills = build_skill(u, basesk, skill_mod);
|
|
made = build_limited(u, con, completed, want, basesk, &skills);
|
|
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
|
|
if (made > 0) {
|
|
produceexp(u, con->skill, (made < u->number) ? made : u->number);
|
|
}
|
|
return made;
|
|
}
|
|
|
|
message *msg_materials_required(unit * u, order * ord,
|
|
const construction * ctype, int multi)
|
|
{
|
|
int c;
|
|
message *msg;
|
|
/* something missing from the list of materials */
|
|
resource *reslist = NULL;
|
|
|
|
if (multi <= 0 || multi == INT_MAX)
|
|
multi = 1;
|
|
for (c = 0; ctype && ctype->materials[c].number; ++c) {
|
|
resource *res = malloc(sizeof(resource));
|
|
if (!res) abort();
|
|
res->number = multi * ctype->materials[c].number / ctype->reqsize;
|
|
res->type = ctype->materials[c].rtype;
|
|
res->next = reslist;
|
|
reslist = res;
|
|
}
|
|
msg = msg_feedback(u, ord, "build_required", "required", reslist);
|
|
while (reslist) {
|
|
resource *res = reslist->next;
|
|
free(reslist);
|
|
reslist = res;
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
int maxbuild(const unit * u, const construction * cons)
|
|
/* calculate maximum size that can be built from available material */
|
|
{
|
|
int c;
|
|
int maximum = INT_MAX;
|
|
for (c = 0; cons->materials[c].number; c++) {
|
|
const resource_type *rtype = cons->materials[c].rtype;
|
|
int have = get_pooled(u, rtype, GET_DEFAULT, INT_MAX);
|
|
int need = required(1, cons->reqsize, cons->materials[c].number);
|
|
if (have < need) {
|
|
return 0;
|
|
}
|
|
else {
|
|
int b = have / need;
|
|
if (maximum > b) maximum = b;
|
|
}
|
|
}
|
|
return maximum;
|
|
}
|
|
|
|
static int build_failure(unit *u, order *ord, const building_type *btype, int want, int err) {
|
|
switch (err) {
|
|
case ECOMPLETE:
|
|
/* the building is already complete */
|
|
cmistake(u, ord, 4, MSG_PRODUCE);
|
|
break;
|
|
case ENOMATERIALS:
|
|
ADDMSG(&u->faction->msgs, msg_materials_required(u, ord,
|
|
btype->stages->construction, want));
|
|
break;
|
|
case ELOWSKILL:
|
|
case ENEEDSKILL:
|
|
/* no skill, or not enough skill points to build */
|
|
cmistake(u, ord, 50, MSG_PRODUCE);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int build_stages(unit *u, const building_type *btype, int built, int n, int basesk, int *skill_total) {
|
|
|
|
const building_stage *stage;
|
|
int made = 0;
|
|
|
|
for (stage = btype->stages; stage; stage = stage->next) {
|
|
const construction * con = stage->construction;
|
|
if (con->maxsize < 0 || con->maxsize > built) {
|
|
int err, want = INT_MAX;
|
|
if (n < INT_MAX) {
|
|
/* do not build more than n total */
|
|
want = n - made;
|
|
}
|
|
if (con->maxsize > 0) {
|
|
/* do not build more than the rest of the stage */
|
|
int todo = con->maxsize - built;
|
|
if (todo < want) {
|
|
want = todo;
|
|
}
|
|
}
|
|
err = build_limited(u, con, built, want, basesk, skill_total);
|
|
if (err < 0) {
|
|
if (made == 0) {
|
|
/* could not make any part at all */
|
|
return err;
|
|
}
|
|
else {
|
|
/* could not build any part of this stage (low skill, etc). */
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
/* err is the amount we built of this stage */
|
|
made += err;
|
|
if (err != con->maxsize && con->maxsize > 0) {
|
|
/* we did not finish the stage, can quit here */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* build the next stage: */
|
|
if (built >= con->maxsize && con->maxsize > 0) {
|
|
built -= con->maxsize;
|
|
}
|
|
}
|
|
return made;
|
|
}
|
|
|
|
int
|
|
build_building(unit * u, const building_type * btype, int id, int want, order * ord)
|
|
{
|
|
region *r = u->region;
|
|
int n = want, built = 0;
|
|
building *b = NULL;
|
|
/* einmalige Korrektur */
|
|
const char *btname;
|
|
order *new_order = NULL;
|
|
const struct locale *lang = u->faction->locale;
|
|
int skills, basesk; /* number of skill points remainig */
|
|
|
|
assert(u->number);
|
|
assert(btype->stages && btype->stages->construction);
|
|
|
|
basesk = effskill(u, SK_BUILDING, NULL);
|
|
skills = build_skill(u, basesk, 0);
|
|
if (skills == 0) {
|
|
cmistake(u, ord, 101, MSG_PRODUCE);
|
|
return 0;
|
|
}
|
|
|
|
/* 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! */
|
|
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 0;
|
|
}
|
|
}
|
|
}
|
|
else if (u->building && u->building->type == btype) {
|
|
b = u->building;
|
|
}
|
|
|
|
if (b) {
|
|
btype = b->type;
|
|
}
|
|
|
|
if (fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype, false)) {
|
|
/* only one of these per region */
|
|
cmistake(u, ord, 93, MSG_PRODUCE);
|
|
return 0;
|
|
}
|
|
if (btype->flags & BTF_NOBUILD) {
|
|
/* special building, cannot be built */
|
|
cmistake(u, ord, 221, MSG_PRODUCE);
|
|
return 0;
|
|
}
|
|
if ((r->terrain->flags & LAND_REGION) == 0) {
|
|
/* special terrain, cannot build */
|
|
cmistake(u, ord, 221, MSG_PRODUCE);
|
|
return 0;
|
|
}
|
|
if (btype->flags & BTF_ONEPERTURN) {
|
|
if (b && fval(b, BLD_EXPANDED)) {
|
|
cmistake(u, ord, 318, MSG_PRODUCE);
|
|
return 0;
|
|
}
|
|
n = 1;
|
|
}
|
|
if (b) {
|
|
bool rule_other = config_get_int("rules.build.other_buildings", 1) != 0;
|
|
if (!rule_other) {
|
|
unit *owner = building_owner(b);
|
|
if (!owner || owner->faction != u->faction) {
|
|
cmistake(u, ord, 1222, MSG_PRODUCE);
|
|
return 0;
|
|
}
|
|
}
|
|
built = b->size;
|
|
}
|
|
if (n <= 0 || n == INT_MAX) {
|
|
if (b == NULL) {
|
|
if (btype->maxsize > 0) {
|
|
n = btype->maxsize - built;
|
|
}
|
|
else {
|
|
n = INT_MAX;
|
|
}
|
|
}
|
|
else {
|
|
if (b->type->maxsize > 0) {
|
|
n = b->type->maxsize - built;
|
|
}
|
|
else {
|
|
n = INT_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
built = build_stages(u, btype, built, n, basesk, &skills);
|
|
|
|
if (built < 0) {
|
|
return build_failure(u, ord, btype, want, built);
|
|
}
|
|
|
|
if (b) {
|
|
b->size += built;
|
|
} else {
|
|
/* build a new building */
|
|
b = new_building(btype, r, lang);
|
|
b->size = built;
|
|
b->type = btype;
|
|
fset(b, BLD_MAINTAINED);
|
|
|
|
/* Die Einheit befindet sich automatisch im Inneren der neuen Burg. */
|
|
if (u->number && leave(u, false)) {
|
|
u_set_building(u, b);
|
|
}
|
|
}
|
|
|
|
btname = LOC(lang, btype->_name);
|
|
|
|
if (want <= built) {
|
|
/* gebaeude fertig */
|
|
new_order = default_order(lang);
|
|
}
|
|
else if (want != INT_MAX && btname) {
|
|
/* reduzierte restgroesse */
|
|
const char *hasspace = strchr(btname, ' ');
|
|
if (hasspace) {
|
|
new_order =
|
|
create_order(K_MAKE, lang, "%d \"%s\" %i", n - built, btname, b->no);
|
|
}
|
|
else {
|
|
new_order =
|
|
create_order(K_MAKE, lang, "%d %s %i", n - built, btname, b->no);
|
|
}
|
|
}
|
|
else if (btname) {
|
|
/* Neues Haus, Befehl mit Gebaeudename */
|
|
const char *hasspace = strchr(btname, ' ');
|
|
if (hasspace) {
|
|
new_order = create_order(K_MAKE, lang, "\"%s\" %i", btname, b->no);
|
|
}
|
|
else {
|
|
new_order = create_order(K_MAKE, lang, "%s %i", btname, b->no);
|
|
}
|
|
}
|
|
|
|
if (new_order) {
|
|
replace_order(&u->orders, ord, new_order);
|
|
free_order(new_order);
|
|
}
|
|
|
|
ADDMSG(&u->faction->msgs, msg_message("buildbuilding",
|
|
"building unit size", b, u, built));
|
|
|
|
if (b->type->maxsize > 0 && b->size > b->type->maxsize) {
|
|
log_error("build: %s has size=%d, maxsize=%d", buildingname(b), b->size, b->type->maxsize);
|
|
}
|
|
fset(b, BLD_EXPANDED);
|
|
|
|
if (is_lighthouse(btype)) {
|
|
update_lighthouse(b);
|
|
}
|
|
|
|
return 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;
|
|
int can = build(u, construction, size, want, 0);
|
|
|
|
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 = can * DAMAGE_SCALE;
|
|
if (repair > sh->damage) repair = sh->damage;
|
|
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(unit * u, const struct ship_type *newtype, int want,
|
|
order * ord)
|
|
{
|
|
ship *sh;
|
|
int msize;
|
|
const construction *cons = newtype->construction;
|
|
order *new_order;
|
|
region * r = u->region;
|
|
|
|
if (!effskill(u, SK_SHIPBUILDING, NULL)) {
|
|
cmistake(u, ord, 100, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
|
|
/* check if skill and material for 1 size is available */
|
|
if (effskill(u, cons->skill, NULL) < cons->minskill) {
|
|
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
|
|
"error_build_skill_low", "value", cons->minskill));
|
|
return;
|
|
}
|
|
|
|
msize = maxbuild(u, cons);
|
|
if (msize == 0) {
|
|
cmistake(u, ord, 88, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
if (want <= 0 || want > msize) {
|
|
want = msize;
|
|
}
|
|
|
|
sh = new_ship(newtype, r, u->faction->locale);
|
|
|
|
if (leave(u, false)) {
|
|
if (fval(u_race(u), RCF_CANSAIL)) {
|
|
u_set_ship(u, sh);
|
|
}
|
|
}
|
|
new_order =
|
|
create_order(K_MAKE, u->faction->locale, "%s %i", LOC(u->faction->locale,
|
|
parameters[P_SHIP]), sh->no);
|
|
replace_order(&u->orders, ord, new_order);
|
|
free_order(new_order);
|
|
|
|
build_ship(u, sh, want);
|
|
}
|
|
|
|
void continue_ship(unit * u, int want)
|
|
{
|
|
const construction *cons;
|
|
ship *sh;
|
|
int msize;
|
|
region * r = u->region;
|
|
|
|
if (!effskill(u, SK_SHIPBUILDING, NULL)) {
|
|
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;
|
|
if (sh->size == cons->maxsize && !sh->damage) {
|
|
cmistake(u, u->thisorder, 16, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
if (effskill(u, cons->skill, NULL) < cons->minskill) {
|
|
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
|
|
"error_build_skill_low", "value", cons->minskill));
|
|
return;
|
|
}
|
|
msize = maxbuild(u, cons);
|
|
if (msize == 0) {
|
|
cmistake(u, u->thisorder, 88, MSG_PRODUCE);
|
|
return;
|
|
}
|
|
if (want <= 0 || want > msize) {
|
|
want = msize;
|
|
}
|
|
|
|
build_ship(u, sh, want);
|
|
}
|
|
|
|
void free_construction(struct construction *cons)
|
|
{
|
|
free(cons->materials);
|
|
free(cons);
|
|
}
|