forked from github/server
3232 lines
91 KiB
C
3232 lines
91 KiB
C
/*
|
||
Copyright (c) 1998-2014,
|
||
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.
|
||
**/
|
||
|
||
#include <platform.h>
|
||
#include <kernel/config.h>
|
||
#include "economy.h"
|
||
|
||
#include "alchemy.h"
|
||
#include "direction.h"
|
||
#include "donations.h"
|
||
#include "give.h"
|
||
#include "laws.h"
|
||
#include "randenc.h"
|
||
#include "spy.h"
|
||
#include "study.h"
|
||
#include "move.h"
|
||
#include "monster.h"
|
||
#include "morale.h"
|
||
#include "reports.h"
|
||
#include "calendar.h"
|
||
|
||
/* kernel includes */
|
||
#include <kernel/ally.h>
|
||
#include <kernel/building.h>
|
||
#include <kernel/curse.h>
|
||
#include <kernel/equipment.h>
|
||
#include <kernel/faction.h>
|
||
#include <kernel/item.h>
|
||
#include <kernel/messages.h>
|
||
#include <kernel/order.h>
|
||
#include <kernel/plane.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>
|
||
|
||
/* util includes */
|
||
#include <util/attrib.h>
|
||
#include <util/base36.h>
|
||
#include <util/bsdstring.h>
|
||
#include <util/event.h>
|
||
#include <util/goodies.h>
|
||
#include <util/language.h>
|
||
#include <util/lists.h>
|
||
#include <util/log.h>
|
||
#include <util/parser.h>
|
||
#include <util/rng.h>
|
||
|
||
#include <attributes/reduceproduction.h>
|
||
#include <attributes/racename.h>
|
||
#include <attributes/orcification.h>
|
||
|
||
/* libs includes */
|
||
#include <math.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <assert.h>
|
||
#include <limits.h>
|
||
|
||
typedef struct request {
|
||
struct request *next;
|
||
struct unit *unit;
|
||
struct order *ord;
|
||
int qty;
|
||
int no;
|
||
union {
|
||
bool goblin; /* stealing */
|
||
const struct luxury_type *ltype; /* trading */
|
||
} type;
|
||
} request;
|
||
|
||
static int working;
|
||
|
||
static request entertainers[1024];
|
||
static request *nextentertainer;
|
||
static int entertaining;
|
||
|
||
static unsigned int norders;
|
||
static request *oa;
|
||
|
||
#define RECRUIT_MERGE 1
|
||
static int rules_recruit = -1;
|
||
|
||
static void recruit_init(void)
|
||
{
|
||
if (rules_recruit < 0) {
|
||
rules_recruit = 0;
|
||
if (config_get_int("recruit.allow_merge", 1)) {
|
||
rules_recruit |= RECRUIT_MERGE;
|
||
}
|
||
}
|
||
}
|
||
|
||
int income(const unit * u)
|
||
{
|
||
// TODO: make this a property, like race.income, no hard-coding of values
|
||
if (fval(u_race(u), RCF_DRAGON)) {
|
||
switch (old_race(u_race(u))) {
|
||
case RC_FIREDRAGON:
|
||
return 150 * u->number;
|
||
case RC_DRAGON:
|
||
return 1000 * u->number;
|
||
case RC_WYRM:
|
||
return 5000 * u->number;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
return 20 * u->number;
|
||
}
|
||
|
||
static void scramble(void *data, unsigned int n, size_t width)
|
||
{
|
||
unsigned int j;
|
||
char temp[64];
|
||
assert(width <= sizeof(temp));
|
||
for (j = 0; j != n; ++j) {
|
||
unsigned int k = rng_uint() % n;
|
||
if (k == j)
|
||
continue;
|
||
memcpy(temp, (char *)data + j * width, width);
|
||
memcpy((char *)data + j * width, (char *)data + k * width, width);
|
||
memcpy((char *)data + k * width, temp, width);
|
||
}
|
||
}
|
||
|
||
static void expandorders(region * r, request * requests)
|
||
{
|
||
unit *u;
|
||
request *o;
|
||
|
||
/* Alle Units ohne request haben ein -1, alle units mit orders haben ein
|
||
* 0 hier stehen */
|
||
|
||
for (u = r->units; u; u = u->next)
|
||
u->n = -1;
|
||
|
||
norders = 0;
|
||
|
||
for (o = requests; o; o = o->next) {
|
||
if (o->qty > 0) {
|
||
norders += o->qty;
|
||
}
|
||
}
|
||
|
||
if (norders > 0) {
|
||
int i = 0;
|
||
oa = (request *)calloc(norders, sizeof(request));
|
||
for (o = requests; o; o = o->next) {
|
||
if (o->qty > 0) {
|
||
unsigned int j;
|
||
for (j = o->qty; j; j--) {
|
||
oa[i] = *o;
|
||
oa[i].unit->n = 0;
|
||
i++;
|
||
}
|
||
}
|
||
}
|
||
scramble(oa, norders, sizeof(request));
|
||
}
|
||
else {
|
||
oa = NULL;
|
||
}
|
||
while (requests) {
|
||
request *o = requests->next;
|
||
free_order(requests->ord);
|
||
free(requests);
|
||
requests = o;
|
||
}
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
|
||
static void change_level(unit * u, skill_t sk, int bylevel)
|
||
{
|
||
skill *sv = unit_skill(u, sk);
|
||
assert(bylevel > 0);
|
||
if (sv == 0)
|
||
sv = add_skill(u, sk);
|
||
sk_set(sv, sv->level + bylevel);
|
||
}
|
||
|
||
typedef struct recruitment {
|
||
struct recruitment *next;
|
||
faction *f;
|
||
request *requests;
|
||
int total, assigned;
|
||
} recruitment;
|
||
|
||
/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable request
|
||
* to the faction's struct and to total.
|
||
*/
|
||
static recruitment *select_recruitment(request ** rop,
|
||
int(*quantify) (const struct race *, int), int *total)
|
||
{
|
||
recruitment *recruits = NULL;
|
||
|
||
while (*rop) {
|
||
recruitment *rec = recruits;
|
||
request *ro = *rop;
|
||
unit *u = ro->unit;
|
||
const race *rc = u_race(u);
|
||
int qty = quantify(rc, ro->qty);
|
||
|
||
if (qty < 0) {
|
||
rop = &ro->next; /* skip this one */
|
||
}
|
||
else {
|
||
*rop = ro->next; /* remove this one */
|
||
while (rec && rec->f != u->faction)
|
||
rec = rec->next;
|
||
if (rec == NULL) {
|
||
rec = malloc(sizeof(recruitment));
|
||
rec->f = u->faction;
|
||
rec->total = 0;
|
||
rec->assigned = 0;
|
||
rec->requests = NULL;
|
||
rec->next = recruits;
|
||
recruits = rec;
|
||
}
|
||
*total += qty;
|
||
rec->total += qty;
|
||
ro->next = rec->requests;
|
||
rec->requests = ro;
|
||
}
|
||
}
|
||
return recruits;
|
||
}
|
||
|
||
void add_recruits(unit * u, int number, int wanted)
|
||
{
|
||
region *r = u->region;
|
||
assert(number <= wanted);
|
||
if (number > 0) {
|
||
unit *unew;
|
||
char equipment[64];
|
||
|
||
if (u->number == 0) {
|
||
set_number(u, number);
|
||
u->hp = number * unit_max_hp(u);
|
||
unew = u;
|
||
}
|
||
else {
|
||
unew = create_unit(r, u->faction, number, u_race(u), 0, NULL, u);
|
||
}
|
||
|
||
strlcpy(equipment, "new_", sizeof(equipment));
|
||
strlcat(equipment, u_race(u)->_name, sizeof(equipment));
|
||
strlcat(equipment, "_unit", sizeof(equipment));
|
||
equip_unit(unew, get_equipment(equipment));
|
||
|
||
if (u_race(unew)->ec_flags & ECF_REC_HORSES) {
|
||
change_level(unew, SK_RIDING, 1);
|
||
}
|
||
|
||
if (unew != u) {
|
||
transfermen(unew, u, unew->number);
|
||
remove_unit(&r->units, unew);
|
||
}
|
||
}
|
||
if (number < wanted) {
|
||
ADDMSG(&u->faction->msgs, msg_message("recruit",
|
||
"unit region amount want", u, r, number, wanted));
|
||
}
|
||
}
|
||
|
||
static int any_recruiters(const struct race *rc, int qty)
|
||
{
|
||
return (int)(qty * 2 * rc->recruit_multi);
|
||
}
|
||
|
||
/*static int peasant_recruiters(const struct race *rc, int qty)
|
||
{
|
||
if (rc->ec_flags & ECF_REC_ETHEREAL)
|
||
return -1;
|
||
if (rc->ec_flags & ECF_REC_HORSES)
|
||
return -1;
|
||
return (int)(qty * 2 * rc->recruit_multi);
|
||
}*/
|
||
|
||
static int horse_recruiters(const struct race *rc, int qty)
|
||
{
|
||
if (rc->ec_flags & ECF_REC_ETHEREAL)
|
||
return -1;
|
||
if (rc->ec_flags & ECF_REC_HORSES)
|
||
return (int)(qty * 2.0 * rc->recruit_multi);
|
||
return -1;
|
||
}
|
||
|
||
static int do_recruiting(recruitment * recruits, int available)
|
||
{
|
||
recruitment *rec;
|
||
int recruited = 0;
|
||
|
||
/* try to assign recruits to factions fairly */
|
||
while (available > 0) {
|
||
int n = 0;
|
||
int rest, mintotal = INT_MAX;
|
||
|
||
/* find smallest request */
|
||
for (rec = recruits; rec != NULL; rec = rec->next) {
|
||
int want = rec->total - rec->assigned;
|
||
if (want > 0) {
|
||
if (mintotal > want)
|
||
mintotal = want;
|
||
++n;
|
||
}
|
||
}
|
||
if (n == 0)
|
||
break;
|
||
if (mintotal * n > available) {
|
||
mintotal = available / n;
|
||
}
|
||
rest = available - mintotal * n;
|
||
|
||
/* assign size of smallest request for everyone if possible; in the end roll dice to assign
|
||
* small rest */
|
||
for (rec = recruits; rec != NULL; rec = rec->next) {
|
||
int want = rec->total - rec->assigned;
|
||
|
||
if (want > 0) {
|
||
int get = mintotal;
|
||
if (want > mintotal && rest < n && (rng_int() % n) < rest) {
|
||
--rest;
|
||
++get;
|
||
}
|
||
assert(get <= want);
|
||
available -= get;
|
||
rec->assigned += get;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* do actual recruiting */
|
||
for (rec = recruits; rec != NULL; rec = rec->next) {
|
||
request *req;
|
||
int get = rec->assigned;
|
||
|
||
for (req = rec->requests; req; req = req->next) {
|
||
unit *u = req->unit;
|
||
const race *rc = u_race(u); /* race is set in recruit() */
|
||
int number, dec;
|
||
double multi = 2.0 * rc->recruit_multi;
|
||
|
||
number = _min(req->qty, (int)(get / multi));
|
||
if (rc->recruitcost) {
|
||
int afford = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
|
||
number * rc->recruitcost) / rc->recruitcost;
|
||
number = _min(number, afford);
|
||
}
|
||
if (u->number + number > UNIT_MAXSIZE) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, req->ord, "error_unit_size",
|
||
"maxsize", UNIT_MAXSIZE));
|
||
number = UNIT_MAXSIZE - u->number;
|
||
assert(number >= 0);
|
||
}
|
||
if (rc->recruitcost) {
|
||
use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
|
||
rc->recruitcost * number);
|
||
}
|
||
if (u->number == 0 && fval(u, UFL_DEAD)) {
|
||
/* unit is empty, dead, and cannot recruit */
|
||
number = 0;
|
||
}
|
||
if (number > 0) {
|
||
add_recruits(u, number, req->qty);
|
||
dec = (int)(number * multi);
|
||
if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) {
|
||
recruited += dec;
|
||
}
|
||
|
||
get -= dec;
|
||
}
|
||
}
|
||
}
|
||
return recruited;
|
||
}
|
||
|
||
void free_recruitments(recruitment * recruits)
|
||
{
|
||
while (recruits) {
|
||
recruitment *rec = recruits;
|
||
recruits = rec->next;
|
||
while (rec->requests) {
|
||
request *req = rec->requests;
|
||
rec->requests = req->next;
|
||
free_order(req->ord);
|
||
free(req);
|
||
}
|
||
free(rec);
|
||
}
|
||
}
|
||
|
||
/* Rekrutierung */
|
||
static void expandrecruit(region * r, request * recruitorders)
|
||
{
|
||
recruitment *recruits = NULL;
|
||
|
||
int orc_total = 0;
|
||
|
||
/* centaurs: */
|
||
recruits = select_recruitment(&recruitorders, horse_recruiters, &orc_total);
|
||
if (recruits) {
|
||
int recruited, horses = rhorses(r) * 2;
|
||
if (orc_total < horses)
|
||
horses = orc_total;
|
||
recruited = do_recruiting(recruits, horses);
|
||
rsethorses(r, (horses - recruited) / 2);
|
||
free_recruitments(recruits);
|
||
}
|
||
|
||
/* peasant limited: */
|
||
recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
|
||
if (recruits) {
|
||
int orc_recruited, orc_peasants = rpeasants(r) * 2;
|
||
int orc_frac = orc_peasants / RECRUITFRACTION; /* anzahl orks. 2 ork = 1 bauer */
|
||
if (orc_total < orc_frac)
|
||
orc_frac = orc_total;
|
||
orc_recruited = do_recruiting(recruits, orc_frac);
|
||
assert(orc_recruited <= orc_frac);
|
||
rsetpeasants(r, (orc_peasants - orc_recruited) / 2);
|
||
free_recruitments(recruits);
|
||
}
|
||
|
||
/* no limit: */
|
||
recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
|
||
if (recruits) {
|
||
int recruited, peasants = rpeasants(r) * 2;
|
||
recruited = do_recruiting(recruits, INT_MAX);
|
||
if (recruited > 0) {
|
||
rsetpeasants(r, (peasants - recruited) / 2);
|
||
}
|
||
free_recruitments(recruits);
|
||
}
|
||
|
||
assert(recruitorders == NULL);
|
||
}
|
||
|
||
static int recruit_cost(const faction * f, const race * rc)
|
||
{
|
||
if (is_monsters(f) || f->race == rc) {
|
||
return rc->recruitcost;
|
||
}
|
||
else if (valid_race(f, rc)) {
|
||
return rc->recruitcost;
|
||
/* return get_param_int(f->race->parameters, "other_cost", -1); */
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static void recruit(unit * u, struct order *ord, request ** recruitorders)
|
||
{
|
||
region *r = u->region;
|
||
plane *pl;
|
||
request *o;
|
||
int recruitcost = -1;
|
||
const faction *f = u->faction;
|
||
const struct race *rc = u_race(u);
|
||
const char *str;
|
||
int n;
|
||
|
||
init_order(ord);
|
||
n = getint();
|
||
if (n <= 0) {
|
||
syntax_error(u, ord);
|
||
return;
|
||
}
|
||
|
||
if (u->number == 0) {
|
||
char token[128];
|
||
str = gettoken(token, sizeof(token));
|
||
if (str && str[0]) {
|
||
/* Monsters can RECRUIT 15 DRACOID
|
||
* also: secondary race */
|
||
rc = findrace(str, f->locale);
|
||
if (rc != NULL) {
|
||
recruitcost = recruit_cost(f, rc);
|
||
}
|
||
}
|
||
}
|
||
if (recruitcost < 0) {
|
||
rc = u_race(u);
|
||
recruitcost = recruit_cost(f, rc);
|
||
if (recruitcost < 0) {
|
||
recruitcost = INT_MAX;
|
||
}
|
||
}
|
||
assert(rc);
|
||
u_setrace(u, rc);
|
||
|
||
#if GUARD_DISABLES_RECRUIT
|
||
/* this is a very special case because the recruiting unit may be empty
|
||
* at this point and we have to look at the creating unit instead. This
|
||
* is done in cansee, which is called indirectly by is_guarded(). */
|
||
if (is_guarded(r, u, GUARD_RECRUIT)) {
|
||
cmistake(u, ord, 70, MSG_EVENT);
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
if (rc == get_race(RC_INSECT)) {
|
||
gamedate date;
|
||
get_gamedate(turn, &date);
|
||
if (date.season == 0 && r->terrain != newterrain(T_DESERT)) {
|
||
#ifdef INSECT_POTION
|
||
bool usepotion = false;
|
||
unit *u2;
|
||
|
||
for (u2 = r->units; u2; u2 = u2->next)
|
||
if (fval(u2, UFL_WARMTH)) {
|
||
usepotion = true;
|
||
break;
|
||
}
|
||
if (!usepotion)
|
||
#endif
|
||
{
|
||
cmistake(u, ord, 98, MSG_EVENT);
|
||
return;
|
||
}
|
||
}
|
||
/* in Gletschern, Eisbergen gar nicht rekrutieren */
|
||
if (r_insectstalled(r)) {
|
||
cmistake(u, ord, 97, MSG_EVENT);
|
||
return;
|
||
}
|
||
}
|
||
if (is_cursed(r->attribs, C_RIOT, 0)) {
|
||
/* Die Region befindet sich in Aufruhr */
|
||
cmistake(u, ord, 237, MSG_EVENT);
|
||
return;
|
||
}
|
||
|
||
if (!(rc->ec_flags & ECF_REC_HORSES) && fval(r, RF_ORCIFIED)) {
|
||
if (rc != get_race(RC_ORC)) {
|
||
cmistake(u, ord, 238, MSG_EVENT);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (recruitcost) {
|
||
pl = getplane(r);
|
||
if (pl && fval(pl, PFL_NORECRUITS)) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", ""));
|
||
return;
|
||
}
|
||
|
||
if (get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
|
||
recruitcost) < recruitcost) {
|
||
cmistake(u, ord, 142, MSG_EVENT);
|
||
return;
|
||
}
|
||
}
|
||
if (!playerrace(rc)) {
|
||
cmistake(u, ord, 139, MSG_EVENT);
|
||
return;
|
||
}
|
||
|
||
if (fval(u, UFL_HERO)) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_herorecruit", ""));
|
||
return;
|
||
}
|
||
if (has_skill(u, SK_MAGIC)) {
|
||
/* error158;de;{unit} in {region}: '{command}' - Magier arbeiten
|
||
* grunds<64>tzlich nur alleine! */
|
||
cmistake(u, ord, 158, MSG_EVENT);
|
||
return;
|
||
}
|
||
if (has_skill(u, SK_ALCHEMY)
|
||
&& count_skill(u->faction, SK_ALCHEMY) + n >
|
||
skill_limit(u->faction, SK_ALCHEMY)) {
|
||
cmistake(u, ord, 156, MSG_EVENT);
|
||
return;
|
||
}
|
||
if (recruitcost > 0) {
|
||
int pooled =
|
||
get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, recruitcost * n);
|
||
n = _min(n, pooled / recruitcost);
|
||
}
|
||
|
||
u->wants = n;
|
||
|
||
if (!n) {
|
||
cmistake(u, ord, 142, MSG_EVENT);
|
||
return;
|
||
}
|
||
|
||
o = (request *)calloc(1, sizeof(request));
|
||
o->qty = n;
|
||
o->unit = u;
|
||
o->ord = copy_order(ord);
|
||
addlist(recruitorders, o);
|
||
}
|
||
|
||
static void friendly_takeover(region * r, faction * f)
|
||
{
|
||
region_set_owner(r, f, turn);
|
||
morale_change(r, MORALE_TRANSFER);
|
||
}
|
||
|
||
void give_control(unit * u, unit * u2)
|
||
{
|
||
if (u->building) {
|
||
if (u->faction != u2->faction && rule_region_owners()) {
|
||
region *r = u->region;
|
||
faction *f = region_get_owner(r);
|
||
|
||
assert(u->building == u2->building);
|
||
if (f == u->faction) {
|
||
building *b = largestbuilding(r, &cmp_current_owner, false);
|
||
if (b == u->building) {
|
||
friendly_takeover(r, u2->faction);
|
||
}
|
||
}
|
||
}
|
||
building_set_owner(u2);
|
||
}
|
||
if (u->ship) {
|
||
assert(u->ship == u2->ship);
|
||
ship_set_owner(u2);
|
||
}
|
||
}
|
||
|
||
int give_control_cmd(unit * u, order * ord)
|
||
{
|
||
char token[128];
|
||
region *r = u->region;
|
||
unit *u2;
|
||
const char *s;
|
||
|
||
init_order(ord);
|
||
getunit(r, u->faction, &u2);
|
||
|
||
s = gettoken(token, sizeof(token));
|
||
if (s && isparam(s, u->faction->locale, P_CONTROL)) {
|
||
message *msg = 0;
|
||
|
||
if (!can_give_to(u, u2)) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, ord, "feedback_unit_not_found", ""));
|
||
return 0;
|
||
}
|
||
else if (!u->building && !u->ship) {
|
||
msg = cmistake(u, ord, 140, MSG_EVENT);
|
||
}
|
||
else if (u->building) {
|
||
if (u != building_owner(u->building)) {
|
||
msg = cmistake(u, ord, 49, MSG_EVENT);
|
||
}
|
||
else if (u2->building != u->building) {
|
||
msg = cmistake(u, ord, 33, MSG_EVENT);
|
||
}
|
||
}
|
||
else if (u->ship) {
|
||
if (u != ship_owner(u->ship)) {
|
||
msg = cmistake(u, ord, 49, MSG_EVENT);
|
||
}
|
||
else if (u2->ship != u->ship) {
|
||
msg = cmistake(u, ord, 32, MSG_EVENT);
|
||
}
|
||
}
|
||
if (!msg) {
|
||
give_control(u, u2);
|
||
msg = msg_message("givecommand", "unit recipient", u, u2);
|
||
add_message(&u->faction->msgs, msg);
|
||
if (u->faction != u2->faction) {
|
||
add_message(&u2->faction->msgs, msg);
|
||
}
|
||
msg_release(msg);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int forget_cmd(unit * u, order * ord)
|
||
{
|
||
char token[128];
|
||
skill_t sk;
|
||
const char *s;
|
||
|
||
if (is_cursed(u->attribs, C_SLAVE, 0)) {
|
||
/* charmed units shouldn't be losing their skills */
|
||
return 0;
|
||
}
|
||
|
||
init_order(ord);
|
||
s = gettoken(token, sizeof(token));
|
||
|
||
if ((sk = get_skill(s, u->faction->locale)) != NOSKILL) {
|
||
ADDMSG(&u->faction->msgs, msg_message("forget", "unit skill", u, sk));
|
||
set_level(u, sk, 0);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int maintain(building * b)
|
||
{
|
||
int c;
|
||
region *r = b->region;
|
||
bool paid = true;
|
||
unit *u;
|
||
|
||
if (fval(b, BLD_MAINTAINED) || b->type == NULL || b->type->maintenance == NULL) {
|
||
return BLD_MAINTAINED;
|
||
}
|
||
if (fval(b, BLD_DONTPAY)) {
|
||
return 0;
|
||
}
|
||
u = building_owner(b);
|
||
if (u == NULL) {
|
||
/* no owner - send a message to the entire region */
|
||
ADDMSG(&r->msgs, msg_message("maintenance_noowner", "building", b));
|
||
return 0;
|
||
}
|
||
/* If the owner is the region owner, check if dontpay flag is set for the building where he is in */
|
||
if (config_token("rules.region_owner_pay_building", b->type->_name)) {
|
||
if (fval(u->building, BLD_DONTPAY)) {
|
||
return 0;
|
||
}
|
||
}
|
||
for (c = 0; b->type->maintenance[c].number && paid; ++c) {
|
||
const maintenance *m = b->type->maintenance + c;
|
||
int need = m->number;
|
||
|
||
if (fval(m, MTF_VARIABLE))
|
||
need = need * b->size;
|
||
need -= get_pooled(u, m->rtype, GET_DEFAULT, need);
|
||
if (need > 0) {
|
||
paid = false;
|
||
}
|
||
}
|
||
if (fval(b, BLD_DONTPAY)) {
|
||
ADDMSG(&r->msgs, msg_message("maintenance_nowork", "building", b));
|
||
return 0;
|
||
}
|
||
if (!paid) {
|
||
ADDMSG(&u->faction->msgs, msg_message("maintenancefail", "unit building", u, b));
|
||
ADDMSG(&r->msgs, msg_message("maintenance_nowork", "building", b));
|
||
return 0;
|
||
}
|
||
for (c = 0; b->type->maintenance[c].number; ++c) {
|
||
const maintenance *m = b->type->maintenance + c;
|
||
int cost = m->number;
|
||
|
||
if (fval(m, MTF_VARIABLE)) {
|
||
cost = cost * b->size;
|
||
}
|
||
cost -=
|
||
use_pooled(u, m->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK,
|
||
cost);
|
||
assert(cost == 0);
|
||
}
|
||
ADDMSG(&u->faction->msgs, msg_message("maintenance", "unit building", u, b));
|
||
return BLD_MAINTAINED;
|
||
}
|
||
|
||
void maintain_buildings(region * r)
|
||
{
|
||
const curse_type *nocost_ct = ct_find("nocostbuilding");
|
||
building **bp = &r->buildings;
|
||
while (*bp) {
|
||
building *b = *bp;
|
||
int flags = BLD_MAINTAINED;
|
||
|
||
if (!curse_active(get_curse(b->attribs, nocost_ct))) {
|
||
flags = maintain(b);
|
||
}
|
||
fset(b, flags);
|
||
bp = &b->next;
|
||
}
|
||
}
|
||
|
||
void economics(region * r)
|
||
{
|
||
unit *u;
|
||
request *recruitorders = NULL;
|
||
|
||
/* Geben vor Selbstmord (doquit)! Hier alle unmittelbaren Befehle.
|
||
* Rekrutieren vor allen Einnahmequellen. Bewachen JA vor Steuern
|
||
* eintreiben. */
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
order *ord;
|
||
if (u->number > 0) {
|
||
for (ord = u->orders; ord; ord = ord->next) {
|
||
keyword_t kwd = getkeyword(ord);
|
||
if (kwd == K_GIVE) {
|
||
give_cmd(u, ord);
|
||
}
|
||
else if (kwd == K_FORGET) {
|
||
forget_cmd(u, ord);
|
||
}
|
||
if (u->orders == NULL) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
/* RECRUIT orders */
|
||
|
||
if (rules_recruit < 0)
|
||
recruit_init();
|
||
for (u = r->units; u; u = u->next) {
|
||
order *ord;
|
||
|
||
if ((rules_recruit & RECRUIT_MERGE) || u->number == 0) {
|
||
for (ord = u->orders; ord; ord = ord->next) {
|
||
if (getkeyword(ord) == K_RECRUIT) {
|
||
recruit(u, ord, &recruitorders);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (recruitorders) {
|
||
expandrecruit(r, recruitorders);
|
||
}
|
||
remove_empty_units_in_region(r);
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
order *ord = u->thisorder;
|
||
keyword_t kwd = getkeyword(ord);
|
||
if (kwd == K_DESTROY) {
|
||
if (destroy_cmd(u, ord) == 0) {
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
|
||
static void manufacture(unit * u, const item_type * itype, int want)
|
||
{
|
||
int n;
|
||
int skill;
|
||
int minskill = itype->construction->minskill;
|
||
skill_t sk = itype->construction->skill;
|
||
|
||
skill = effskill(u, sk, 0);
|
||
skill =
|
||
skillmod(itype->rtype->attribs, u, u->region, sk, skill, SMF_PRODUCTION);
|
||
|
||
if (skill < 0) {
|
||
/* an error occured */
|
||
int err = -skill;
|
||
cmistake(u, u->thisorder, err, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
|
||
if (want == 0) {
|
||
want = maxbuild(u, itype->construction);
|
||
}
|
||
n = build(u, itype->construction, 0, want);
|
||
switch (n) {
|
||
case ENEEDSKILL:
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "skill_needed", "skill", sk));
|
||
return;
|
||
case EBUILDINGREQ:
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "building_needed", "building",
|
||
itype->construction->btype->_name));
|
||
return;
|
||
case ELOWSKILL:
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "manufacture_skills",
|
||
"skill minskill product", sk, minskill, itype->rtype, 1));
|
||
return;
|
||
case ENOMATERIALS:
|
||
ADDMSG(&u->faction->msgs, msg_materials_required(u, u->thisorder,
|
||
itype->construction, want));
|
||
return;
|
||
}
|
||
if (n > 0) {
|
||
i_change(&u->items, itype, n);
|
||
if (want == INT_MAX)
|
||
want = n;
|
||
ADDMSG(&u->faction->msgs, msg_message("produce",
|
||
"unit region amount wanted resource", u, u->region, n, want,
|
||
itype->rtype));
|
||
}
|
||
else {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, "error_cannotmake",
|
||
""));
|
||
}
|
||
}
|
||
|
||
typedef struct allocation {
|
||
struct allocation *next;
|
||
int want, get;
|
||
double save;
|
||
unsigned int flags;
|
||
unit *unit;
|
||
} allocation;
|
||
#define new_allocation() calloc(sizeof(allocation), 1)
|
||
#define free_allocation(a) free(a)
|
||
|
||
typedef struct allocation_list {
|
||
struct allocation_list *next;
|
||
allocation *data;
|
||
const resource_type *type;
|
||
} allocation_list;
|
||
|
||
static allocation_list *allocations;
|
||
|
||
static bool can_guard(const unit * guard, const unit * u)
|
||
{
|
||
if (fval(guard, UFL_ISNEW))
|
||
return false;
|
||
if (guard->number <= 0 || !cansee(guard->faction, guard->region, u, 0))
|
||
return false;
|
||
if (besieged(guard) || !(fval(u_race(guard), RCF_UNARMEDGUARD)
|
||
|| armedmen(guard, true)))
|
||
return false;
|
||
|
||
return !alliedunit(guard, u->faction, HELP_GUARD);
|
||
}
|
||
|
||
enum {
|
||
AFL_DONE = 1 << 0,
|
||
AFL_LOWSKILL = 1 << 1
|
||
};
|
||
|
||
static void allocate_resource(unit * u, const resource_type * rtype, int want)
|
||
{
|
||
const item_type *itype = resource2item(rtype);
|
||
region *r = u->region;
|
||
int dm = 0;
|
||
allocation_list *alist;
|
||
allocation *al;
|
||
attrib *a = a_find(rtype->attribs, &at_resourcelimit);
|
||
resource_limit *rdata = (resource_limit *)a->data.v;
|
||
const resource_type *rring;
|
||
int amount, skill;
|
||
|
||
/* momentan kann man keine ressourcen abbauen, wenn man daf<61>r
|
||
* Materialverbrauch hat: */
|
||
assert(itype != NULL && (itype->construction == NULL
|
||
|| itype->construction->materials == NULL));
|
||
assert(rdata != NULL);
|
||
|
||
if (rdata->limit != NULL) {
|
||
int avail = rdata->limit(r, rtype);
|
||
if (avail <= 0) {
|
||
cmistake(u, u->thisorder, 121, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (besieged(u)) {
|
||
cmistake(u, u->thisorder, 60, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
|
||
if (rdata->modifiers) {
|
||
resource_mod *mod = rdata->modifiers;
|
||
for (; mod->flags != 0; ++mod) {
|
||
if (mod->flags & RMF_REQUIREDBUILDING) {
|
||
struct building *b = inside_building(u);
|
||
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
|
||
if (mod->btype && mod->btype != btype) {
|
||
cmistake(u, u->thisorder, 104, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rdata->guard != 0) {
|
||
unit *u2;
|
||
for (u2 = r->units; u2; u2 = u2->next) {
|
||
if (is_guard(u2, rdata->guard) != 0 && can_guard(u2, u)) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "region_guarded", "guard", u2));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Bergw<67>chter k<>nnen Abbau von Eisen/Laen durch Bewachen verhindern.
|
||
* Als magische Wesen 'sehen' Bergw<67>chter alles und werden durch
|
||
* Belagerung nicht aufgehalten. (Ansonsten wie oben bei Elfen anpassen).
|
||
*/
|
||
if (itype->rtype && (itype->rtype == get_resourcetype(R_IRON) || itype->rtype == rt_find("laen"))) {
|
||
unit *u2;
|
||
for (u2 = r->units; u2; u2 = u2->next) {
|
||
if (is_guard(u, GUARD_MINING)
|
||
&& !fval(u2, UFL_ISNEW)
|
||
&& u2->number && !alliedunit(u2, u->faction, HELP_GUARD)) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "region_guarded", "guard", u2));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
assert(itype->construction->skill != 0
|
||
|| "limited resource needs a required skill for making it");
|
||
skill = effskill(u, itype->construction->skill, 0);
|
||
if (skill == 0) {
|
||
skill_t sk = itype->construction->skill;
|
||
add_message(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "skill_needed", "skill", sk));
|
||
return;
|
||
}
|
||
if (skill < itype->construction->minskill) {
|
||
skill_t sk = itype->construction->skill;
|
||
add_message(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "manufacture_skills",
|
||
"skill minskill product", sk, itype->construction->minskill,
|
||
itype->rtype));
|
||
return;
|
||
}
|
||
else {
|
||
struct building *b = inside_building(u);
|
||
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
|
||
|
||
if (rdata->modifiers) {
|
||
resource_mod *mod = rdata->modifiers;
|
||
for (; mod->flags != 0; ++mod) {
|
||
if (mod->flags & RMF_SKILL) {
|
||
if (mod->btype == NULL || mod->btype == btype) {
|
||
if (mod->race == NULL || mod->race == u_race(u)) {
|
||
skill += mod->value.i;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
amount = skill * u->number;
|
||
/* nun ist amount die Gesamtproduktion der Einheit (in punkten) */
|
||
|
||
/* mit Flinkfingerring verzehnfacht sich die Produktion */
|
||
rring = get_resourcetype(R_RING_OF_NIMBLEFINGER);
|
||
if (rring) {
|
||
int dm = i_get(u->items, rring->itype);
|
||
amount += skill * _min(u->number, dm) * (roqf_factor() - 1);
|
||
}
|
||
|
||
/* Schaffenstrunk: */
|
||
if ((dm = get_effect(u, oldpotiontype[P_DOMORE])) != 0) {
|
||
dm = _min(dm, u->number);
|
||
change_effect(u, oldpotiontype[P_DOMORE], -dm);
|
||
amount += dm * skill; /* dm Personen produzieren doppelt */
|
||
}
|
||
|
||
amount /= itype->construction->minskill;
|
||
|
||
/* Limitierung durch Parameter m. */
|
||
if (want > 0 && want < amount)
|
||
amount = want;
|
||
|
||
alist = allocations;
|
||
while (alist && alist->type != rtype)
|
||
alist = alist->next;
|
||
if (!alist) {
|
||
alist = calloc(sizeof(struct allocation_list), 1);
|
||
alist->next = allocations;
|
||
alist->type = rtype;
|
||
allocations = alist;
|
||
}
|
||
al = new_allocation();
|
||
al->want = amount;
|
||
al->save = 1.0;
|
||
al->next = alist->data;
|
||
al->unit = u;
|
||
alist->data = al;
|
||
|
||
if (rdata->modifiers) {
|
||
struct building *b = inside_building(u);
|
||
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
|
||
|
||
resource_mod *mod = rdata->modifiers;
|
||
for (; mod->flags != 0; ++mod) {
|
||
if (mod->flags & RMF_SAVEMATERIAL) {
|
||
if (mod->btype == NULL || mod->btype == btype) {
|
||
if (mod->race == NULL || mod->race == u_race(u)) {
|
||
al->save *= mod->value.f;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static int required(int want, double save)
|
||
{
|
||
int norders = (int)(want * save);
|
||
if (norders < want * save)
|
||
++norders;
|
||
return norders;
|
||
}
|
||
|
||
static void
|
||
leveled_allocation(const resource_type * rtype, region * r, allocation * alist)
|
||
{
|
||
const item_type *itype = resource2item(rtype);
|
||
rawmaterial *rm = rm_get(r, rtype);
|
||
int need;
|
||
bool first = true;
|
||
|
||
if (rm != NULL) {
|
||
do {
|
||
int avail = rm->amount;
|
||
int norders = 0;
|
||
allocation *al;
|
||
|
||
if (avail <= 0) {
|
||
for (al = alist; al; al = al->next) {
|
||
al->get = 0;
|
||
}
|
||
break;
|
||
}
|
||
|
||
assert(avail > 0);
|
||
|
||
for (al = alist; al; al = al->next)
|
||
if (!fval(al, AFL_DONE)) {
|
||
int req = required(al->want - al->get, al->save);
|
||
assert(al->get <= al->want && al->get >= 0);
|
||
if (effskill(al->unit, itype->construction->skill, 0)
|
||
>= rm->level + itype->construction->minskill - 1) {
|
||
if (req) {
|
||
norders += req;
|
||
}
|
||
else {
|
||
fset(al, AFL_DONE);
|
||
}
|
||
}
|
||
else {
|
||
fset(al, AFL_DONE);
|
||
if (first)
|
||
fset(al, AFL_LOWSKILL);
|
||
}
|
||
}
|
||
need = norders;
|
||
|
||
avail = _min(avail, norders);
|
||
if (need > 0) {
|
||
int use = 0;
|
||
for (al = alist; al; al = al->next)
|
||
if (!fval(al, AFL_DONE)) {
|
||
if (avail > 0) {
|
||
int want = required(al->want - al->get, al->save);
|
||
int x = avail * want / norders;
|
||
/* Wenn Rest, dann w<>rfeln, ob ich was bekomme: */
|
||
if (rng_int() % norders < (avail * want) % norders)
|
||
++x;
|
||
avail -= x;
|
||
use += x;
|
||
norders -= want;
|
||
need -= x;
|
||
al->get = _min(al->want, al->get + (int)(x / al->save));
|
||
}
|
||
}
|
||
if (use) {
|
||
assert(use <= rm->amount);
|
||
rm->type->use(rm, r, use);
|
||
}
|
||
assert(avail == 0 || norders == 0);
|
||
}
|
||
first = false;
|
||
} while (need > 0);
|
||
}
|
||
}
|
||
|
||
static void
|
||
attrib_allocation(const resource_type * rtype, region * r, allocation * alist)
|
||
{
|
||
allocation *al;
|
||
int norders = 0;
|
||
attrib *a = a_find(rtype->attribs, &at_resourcelimit);
|
||
resource_limit *rdata = (resource_limit *)a->data.v;
|
||
int avail = rdata->value;
|
||
|
||
for (al = alist; al; al = al->next) {
|
||
norders += required(al->want, al->save);
|
||
}
|
||
|
||
if (rdata->limit) {
|
||
avail = rdata->limit(r, rtype);
|
||
if (avail < 0)
|
||
avail = 0;
|
||
}
|
||
|
||
avail = _min(avail, norders);
|
||
for (al = alist; al; al = al->next) {
|
||
if (avail > 0) {
|
||
int want = required(al->want, al->save);
|
||
int x = avail * want / norders;
|
||
/* Wenn Rest, dann w<>rfeln, ob ich was bekomme: */
|
||
if (rng_int() % norders < (avail * want) % norders)
|
||
++x;
|
||
avail -= x;
|
||
norders -= want;
|
||
al->get = _min(al->want, (int)(x / al->save));
|
||
if (rdata->produce) {
|
||
int use = required(al->get, al->save);
|
||
if (use)
|
||
rdata->produce(r, rtype, use);
|
||
}
|
||
}
|
||
}
|
||
assert(avail == 0 || norders == 0);
|
||
}
|
||
|
||
typedef void(*allocate_function) (const resource_type *, struct region *,
|
||
struct allocation *);
|
||
|
||
static allocate_function get_allocator(const struct resource_type *rtype)
|
||
{
|
||
attrib *a = a_find(rtype->attribs, &at_resourcelimit);
|
||
|
||
if (a != NULL) {
|
||
resource_limit *rdata = (resource_limit *)a->data.v;
|
||
if (rdata->value > 0 || rdata->limit != NULL) {
|
||
return attrib_allocation;
|
||
}
|
||
return leveled_allocation;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
void split_allocations(region * r)
|
||
{
|
||
allocation_list **p_alist = &allocations;
|
||
freset(r, RF_SELECT);
|
||
while (*p_alist) {
|
||
allocation_list *alist = *p_alist;
|
||
const resource_type *rtype = alist->type;
|
||
allocate_function alloc = get_allocator(rtype);
|
||
const item_type *itype = resource2item(rtype);
|
||
allocation **p_al = &alist->data;
|
||
|
||
freset(r, RF_SELECT);
|
||
alloc(rtype, r, alist->data);
|
||
|
||
while (*p_al) {
|
||
allocation *al = *p_al;
|
||
if (al->get) {
|
||
assert(itype || !"not implemented for non-items");
|
||
i_change(&al->unit->items, itype, al->get);
|
||
produceexp(al->unit, itype->construction->skill, al->unit->number);
|
||
fset(r, RF_SELECT);
|
||
}
|
||
if (al->want == INT_MAX)
|
||
al->want = al->get;
|
||
ADDMSG(&al->unit->faction->msgs, msg_message("produce",
|
||
"unit region amount wanted resource",
|
||
al->unit, al->unit->region, al->get, al->want, rtype));
|
||
*p_al = al->next;
|
||
free_allocation(al);
|
||
}
|
||
*p_alist = alist->next;
|
||
free(alist);
|
||
}
|
||
allocations = NULL;
|
||
}
|
||
|
||
static void create_potion(unit * u, const potion_type * ptype, int want)
|
||
{
|
||
int built;
|
||
|
||
if (want == 0) {
|
||
want = maxbuild(u, ptype->itype->construction);
|
||
}
|
||
built = build(u, ptype->itype->construction, 0, want);
|
||
switch (built) {
|
||
case ELOWSKILL:
|
||
case ENEEDSKILL:
|
||
/* no skill, or not enough skill points to build */
|
||
cmistake(u, u->thisorder, 50, MSG_PRODUCE);
|
||
break;
|
||
case EBUILDINGREQ:
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "building_needed", "building",
|
||
ptype->itype->construction->btype->_name));
|
||
break;
|
||
case ECOMPLETE:
|
||
assert(0);
|
||
break;
|
||
case ENOMATERIALS:
|
||
/* something missing from the list of materials */
|
||
ADDMSG(&u->faction->msgs, msg_materials_required(u, u->thisorder,
|
||
ptype->itype->construction, want));
|
||
return;
|
||
break;
|
||
default:
|
||
i_change(&u->items, ptype->itype, built);
|
||
if (want == INT_MAX)
|
||
want = built;
|
||
ADDMSG(&u->faction->msgs, msg_message("produce",
|
||
"unit region amount wanted resource", u, u->region, built, want,
|
||
ptype->itype->rtype));
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void create_item(unit * u, const item_type * itype, int want)
|
||
{
|
||
if (itype->construction && fval(itype->rtype, RTF_LIMITED)) {
|
||
#if GUARD_DISABLES_PRODUCTION == 1
|
||
if (is_guarded(u->region, u, GUARD_PRODUCE)) {
|
||
cmistake(u, u->thisorder, 70, MSG_EVENT);
|
||
return;
|
||
}
|
||
#endif
|
||
allocate_resource(u, itype->rtype, want);
|
||
}
|
||
else {
|
||
const potion_type *ptype = resource2potion(itype->rtype);
|
||
if (ptype != NULL)
|
||
create_potion(u, ptype, want);
|
||
else if (itype->construction && itype->construction->materials)
|
||
manufacture(u, itype, want);
|
||
else {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
|
||
"error_cannotmake", ""));
|
||
}
|
||
}
|
||
}
|
||
|
||
int make_cmd(unit * u, struct order *ord)
|
||
{
|
||
char token[128];
|
||
region *r = u->region;
|
||
const building_type *btype = 0;
|
||
const ship_type *stype = 0;
|
||
const item_type *itype = 0;
|
||
param_t p = NOPARAM;
|
||
int m = INT_MAX;
|
||
const char *s;
|
||
const struct locale *lang = u->faction->locale;
|
||
char ibuf[16];
|
||
keyword_t kwd;
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_MAKE);
|
||
s = gettoken(token, sizeof(token));
|
||
|
||
if (s) {
|
||
m = atoi((const char *)s);
|
||
sprintf(ibuf, "%d", m);
|
||
if (!strcmp(ibuf, (const char *)s)) {
|
||
/* a quantity was given */
|
||
s = gettoken(token, sizeof(token));
|
||
}
|
||
else {
|
||
m = INT_MAX;
|
||
}
|
||
if (s) {
|
||
p = findparam(s, u->faction->locale);
|
||
}
|
||
}
|
||
|
||
if (p == P_ROAD) {
|
||
plane *pl = rplane(r);
|
||
if (pl && fval(pl, PFL_NOBUILD)) {
|
||
cmistake(u, ord, 275, MSG_PRODUCE);
|
||
}
|
||
else {
|
||
const char * s = gettoken(token, sizeof(token));
|
||
direction_t d = s ? get_direction(s, u->faction->locale) : NODIRECTION;
|
||
if (d != NODIRECTION) {
|
||
build_road(u, m, d);
|
||
}
|
||
else {
|
||
/* Die Richtung wurde nicht erkannt */
|
||
cmistake(u, ord, 71, MSG_PRODUCE);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
else if (p == P_SHIP) {
|
||
plane *pl = rplane(r);
|
||
if (pl && fval(pl, PFL_NOBUILD)) {
|
||
cmistake(u, ord, 276, MSG_PRODUCE);
|
||
}
|
||
else {
|
||
continue_ship(u, m);
|
||
}
|
||
return 0;
|
||
}
|
||
else if (p == P_HERBS) {
|
||
herbsearch(u, m);
|
||
return 0;
|
||
}
|
||
|
||
/* since the string can match several objects, like in 'academy' and
|
||
* 'academy of arts', we need to figure out what the player meant.
|
||
* This is not 100% safe.
|
||
*/
|
||
if (s) {
|
||
stype = findshiptype(s, lang);
|
||
btype = findbuildingtype(s, lang);
|
||
itype = finditemtype(s, lang);
|
||
}
|
||
if (itype != NULL && (btype != NULL || stype != NULL)) {
|
||
if (itype->construction == NULL) {
|
||
/* if the item cannot be made, we probably didn't mean to make it */
|
||
itype = NULL;
|
||
}
|
||
else if (stype != NULL) {
|
||
const char *sname = LOC(lang, stype->_name);
|
||
const char *iname = LOC(lang, resourcename(itype->rtype, 0));
|
||
if (strlen(iname) < strlen(sname))
|
||
stype = NULL;
|
||
else
|
||
itype = NULL;
|
||
}
|
||
else {
|
||
const char *bname = LOC(lang, btype->_name);
|
||
const char *iname = LOC(lang, resourcename(itype->rtype, 0));
|
||
if (strlen(iname) < strlen(bname))
|
||
btype = NULL;
|
||
else
|
||
itype = NULL;
|
||
}
|
||
}
|
||
|
||
if (btype != NULL && stype != NULL) {
|
||
const char *bname = LOC(lang, btype->_name);
|
||
const char *sname = LOC(lang, stype->_name);
|
||
if (strlen(sname) < strlen(bname))
|
||
btype = NULL;
|
||
else
|
||
stype = NULL;
|
||
}
|
||
|
||
if (stype != NOSHIP) {
|
||
plane *pl = rplane(r);
|
||
if (pl && fval(pl, PFL_NOBUILD)) {
|
||
cmistake(u, ord, 276, MSG_PRODUCE);
|
||
}
|
||
else {
|
||
create_ship(u, stype, m, ord);
|
||
}
|
||
}
|
||
else if (btype != NOBUILDING) {
|
||
plane *pl = rplane(r);
|
||
if (pl && fval(pl, PFL_NOBUILD)) {
|
||
cmistake(u, ord, 275, MSG_PRODUCE);
|
||
}
|
||
else if (btype->construction) {
|
||
int id = getid();
|
||
build_building(u, btype, id, m, ord);
|
||
}
|
||
else {
|
||
cmistake(u, ord, 275, MSG_PRODUCE);
|
||
}
|
||
}
|
||
else if (itype != NULL) {
|
||
create_item(u, itype, m);
|
||
}
|
||
else {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_cannotmake", ""));
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
|
||
static void free_luxuries(struct attrib *a)
|
||
{
|
||
item *itm = (item *)a->data.v;
|
||
a->data.v = NULL;
|
||
i_freeall(&itm);
|
||
}
|
||
|
||
const attrib_type at_luxuries = {
|
||
"luxuries", NULL, free_luxuries, NULL, NULL, NULL
|
||
};
|
||
|
||
static void expandbuying(region * r, request * buyorders)
|
||
{
|
||
const resource_type *rsilver = get_resourcetype(R_SILVER);
|
||
int max_products;
|
||
unit *u;
|
||
static struct trade {
|
||
const luxury_type *type;
|
||
int number;
|
||
int multi;
|
||
} trades[MAXLUXURIES], *trade;
|
||
static int ntrades = 0;
|
||
int i;
|
||
const luxury_type *ltype;
|
||
|
||
if (ntrades == 0) {
|
||
for (ntrades = 0, ltype = luxurytypes; ltype; ltype = ltype->next) {
|
||
assert(ntrades < MAXLUXURIES);
|
||
trades[ntrades++].type = ltype;
|
||
}
|
||
}
|
||
for (i = 0; i != ntrades; ++i) {
|
||
trades[i].number = 0;
|
||
trades[i].multi = 1;
|
||
}
|
||
|
||
if (!buyorders)
|
||
return;
|
||
|
||
/* Initialisation. multiplier ist der Multiplikator auf den
|
||
* Verkaufspreis. F<>r max_products Produkte kauft man das Produkt zum
|
||
* einfachen Verkaufspreis, danach erh<72>ht sich der Multiplikator um 1.
|
||
* counter ist ein Z<>hler, der die gekauften Produkte z<>hlt. money
|
||
* wird f<>r die debug message gebraucht. */
|
||
|
||
max_products = rpeasants(r) / TRADE_FRACTION;
|
||
|
||
/* Kauf - auch so programmiert, da<64> er leicht erweiterbar auf mehrere
|
||
* G<>ter pro Monat ist. j sind die Befehle, i der Index des
|
||
* gehandelten Produktes. */
|
||
if (max_products > 0) {
|
||
unsigned int j;
|
||
expandorders(r, buyorders);
|
||
if (!norders)
|
||
return;
|
||
|
||
for (j = 0; j != norders; j++) {
|
||
int price, multi;
|
||
ltype = oa[j].type.ltype;
|
||
trade = trades;
|
||
while (trade->type != ltype)
|
||
++trade;
|
||
multi = trade->multi;
|
||
price = ltype->price * multi;
|
||
|
||
if (get_pooled(oa[j].unit, rsilver, GET_DEFAULT,
|
||
price) >= price) {
|
||
unit *u = oa[j].unit;
|
||
item *items;
|
||
|
||
/* litems z<>hlt die G<>ter, die verkauft wurden, u->n das Geld, das
|
||
* verdient wurde. Dies mu<6D> gemacht werden, weil der Preis st<73>ndig sinkt,
|
||
* man sich also das verdiente Geld und die verkauften Produkte separat
|
||
* merken mu<6D>. */
|
||
attrib *a = a_find(u->attribs, &at_luxuries);
|
||
if (a == NULL)
|
||
a = a_add(&u->attribs, a_new(&at_luxuries));
|
||
|
||
items = a->data.v;
|
||
i_change(&items, ltype->itype, 1);
|
||
a->data.v = items;
|
||
i_change(&oa[j].unit->items, ltype->itype, 1);
|
||
use_pooled(u, rsilver, GET_DEFAULT, price);
|
||
if (u->n < 0)
|
||
u->n = 0;
|
||
u->n += price;
|
||
|
||
rsetmoney(r, rmoney(r) + price);
|
||
|
||
/* Falls mehr als max_products Bauern ein Produkt verkauft haben, steigt
|
||
* der Preis Multiplikator f<>r das Produkt um den Faktor 1. Der Z<>hler
|
||
* wird wieder auf 0 gesetzt. */
|
||
if (++trade->number == max_products) {
|
||
trade->number = 0;
|
||
++trade->multi;
|
||
}
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
}
|
||
free(oa);
|
||
|
||
/* Ausgabe an Einheiten */
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
attrib *a = a_find(u->attribs, &at_luxuries);
|
||
item *itm;
|
||
if (a == NULL)
|
||
continue;
|
||
ADDMSG(&u->faction->msgs, msg_message("buy", "unit money", u, u->n));
|
||
for (itm = (item *)a->data.v; itm; itm = itm->next) {
|
||
if (itm->number) {
|
||
ADDMSG(&u->faction->msgs, msg_message("buyamount",
|
||
"unit amount resource", u, itm->number, itm->type->rtype));
|
||
}
|
||
}
|
||
a_remove(&u->attribs, a);
|
||
}
|
||
}
|
||
}
|
||
|
||
attrib_type at_trades = {
|
||
"trades",
|
||
DEFAULT_INIT,
|
||
DEFAULT_FINALIZE,
|
||
DEFAULT_AGE,
|
||
NO_WRITE,
|
||
NO_READ
|
||
};
|
||
|
||
static void buy(unit * u, request ** buyorders, struct order *ord)
|
||
{
|
||
char token[128];
|
||
region *r = u->region;
|
||
int n, k;
|
||
request *o;
|
||
attrib *a;
|
||
const item_type *itype = NULL;
|
||
const luxury_type *ltype = NULL;
|
||
keyword_t kwd;
|
||
const char *s;
|
||
|
||
if (u->ship && is_guarded(r, u, GUARD_CREWS)) {
|
||
cmistake(u, ord, 69, MSG_INCOME);
|
||
return;
|
||
}
|
||
if (u->ship && is_guarded(r, u, GUARD_CREWS)) {
|
||
cmistake(u, ord, 69, MSG_INCOME);
|
||
return;
|
||
}
|
||
/* Im Augenblick kann man nur 1 Produkt kaufen. expandbuying ist aber
|
||
* schon daf<61>r ausger<65>stet, mehrere Produkte zu kaufen. */
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_BUY);
|
||
n = getint();
|
||
if (n <= 0) {
|
||
cmistake(u, ord, 26, MSG_COMMERCE);
|
||
return;
|
||
}
|
||
if (besieged(u)) {
|
||
/* Belagerte Einheiten k<>nnen nichts kaufen. */
|
||
cmistake(u, ord, 60, MSG_COMMERCE);
|
||
return;
|
||
}
|
||
|
||
if (u_race(u) == get_race(RC_INSECT)) {
|
||
/* entweder man ist insekt, oder... */
|
||
if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT)
|
||
&& !rbuildings(r)) {
|
||
cmistake(u, ord, 119, MSG_COMMERCE);
|
||
return;
|
||
}
|
||
}
|
||
else {
|
||
/* ...oder in der Region mu<6D> es eine Burg geben. */
|
||
building *b = 0;
|
||
if (r->buildings) {
|
||
static int cache;
|
||
static const struct building_type *bt_castle;
|
||
if (bt_changed(&cache)) {
|
||
bt_castle = bt_find("castle");
|
||
}
|
||
|
||
for (b = r->buildings; b; b = b->next) {
|
||
if (b->type == bt_castle && b->size >= 2) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (b == NULL) {
|
||
cmistake(u, ord, 119, MSG_COMMERCE);
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Ein H<>ndler kann nur 10 G<>ter pro Talentpunkt handeln. */
|
||
k = u->number * 10 * effskill(u, SK_TRADE, 0);
|
||
|
||
/* hat der H<>ndler bereits gehandelt, muss die Menge der bereits
|
||
* verkauften/gekauften G<>ter abgezogen werden */
|
||
a = a_find(u->attribs, &at_trades);
|
||
if (!a) {
|
||
a = a_add(&u->attribs, a_new(&at_trades));
|
||
}
|
||
else {
|
||
k -= a->data.i;
|
||
}
|
||
|
||
n = _min(n, k);
|
||
|
||
if (!n) {
|
||
cmistake(u, ord, 102, MSG_COMMERCE);
|
||
return;
|
||
}
|
||
|
||
assert(n >= 0);
|
||
/* die Menge der verkauften G<>ter merken */
|
||
a->data.i += n;
|
||
|
||
s = gettoken(token, sizeof(token));
|
||
itype = s ? finditemtype(s, u->faction->locale) : 0;
|
||
if (itype != NULL) {
|
||
ltype = resource2luxury(itype->rtype);
|
||
if (ltype == NULL) {
|
||
cmistake(u, ord, 124, MSG_COMMERCE);
|
||
return;
|
||
}
|
||
}
|
||
if (r_demand(r, ltype)) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "luxury_notsold", ""));
|
||
return;
|
||
}
|
||
o = (request *)calloc(1, sizeof(request));
|
||
o->type.ltype = ltype; /* sollte immer gleich sein */
|
||
|
||
o->unit = u;
|
||
o->qty = n;
|
||
addlist(buyorders, o);
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
static void add_income(unit * u, int type, int want, int qty)
|
||
{
|
||
if (want == INT_MAX)
|
||
want = qty;
|
||
ADDMSG(&u->faction->msgs, msg_message("income",
|
||
"unit region mode wanted amount", u, u->region, type, want, qty));
|
||
}
|
||
|
||
/* Steuers<72>tze in % bei Burggr<67><72>e */
|
||
static int tax_per_size[7] = { 0, 6, 12, 18, 24, 30, 36 };
|
||
|
||
static void expandselling(region * r, request * sellorders, int limit)
|
||
{
|
||
int money, price, max_products;
|
||
unsigned int j;
|
||
/* int m, n = 0; */
|
||
int maxsize = 0, maxeffsize = 0;
|
||
int taxcollected = 0;
|
||
int hafencollected = 0;
|
||
unit *maxowner = (unit *)NULL;
|
||
building *maxb = (building *)NULL;
|
||
building *b;
|
||
unit *u;
|
||
unit *hafenowner;
|
||
static int counter[MAXLUXURIES];
|
||
static int ncounter = 0;
|
||
static int bt_cache;
|
||
static const struct building_type *castle_bt, *harbour_bt, *caravan_bt;
|
||
|
||
if (bt_changed(&bt_cache)) {
|
||
castle_bt = bt_find("castle");
|
||
harbour_bt = bt_find("harbour");
|
||
caravan_bt = bt_find("caravan");
|
||
}
|
||
if (ncounter == 0) {
|
||
const luxury_type *ltype;
|
||
for (ltype = luxurytypes; ltype; ltype = ltype->next) {
|
||
assert(ncounter < MAXLUXURIES);
|
||
++ncounter;
|
||
}
|
||
}
|
||
memset(counter, 0, sizeof(int) * ncounter);
|
||
|
||
if (!sellorders) { /* NEIN, denn Insekten k<>nnen in || !r->buildings) */
|
||
return; /* S<>mpfen und W<>sten auch so handeln */
|
||
}
|
||
/* Stelle Eigent<6E>mer der gr<67><72>ten Burg fest. Bekommt Steuern aus jedem
|
||
* Verkauf. Wenn zwei Burgen gleicher Gr<47><72>e bekommt gar keiner etwas. */
|
||
for (b = rbuildings(r); b; b = b->next) {
|
||
if (b->size > maxsize && building_owner(b) != NULL
|
||
&& b->type == castle_bt) {
|
||
maxb = b;
|
||
maxsize = b->size;
|
||
maxowner = building_owner(b);
|
||
}
|
||
else if (b->size == maxsize && b->type == castle_bt) {
|
||
maxb = (building *)NULL;
|
||
maxowner = (unit *)NULL;
|
||
}
|
||
}
|
||
|
||
hafenowner = owner_buildingtyp(r, harbour_bt);
|
||
|
||
if (maxb != (building *)NULL && maxowner != (unit *)NULL) {
|
||
maxeffsize = buildingeffsize(maxb, false);
|
||
if (maxeffsize == 0) {
|
||
maxb = (building *)NULL;
|
||
maxowner = (unit *)NULL;
|
||
}
|
||
}
|
||
/* Die Region muss genug Geld haben, um die Produkte kaufen zu k<>nnen. */
|
||
|
||
money = rmoney(r);
|
||
|
||
/* max_products sind 1/100 der Bev<65>lkerung, falls soviele Produkte
|
||
* verkauft werden - counter[] - sinkt die Nachfrage um 1 Punkt.
|
||
* multiplier speichert r->demand f<>r die debug message ab. */
|
||
|
||
max_products = rpeasants(r) / TRADE_FRACTION;
|
||
if (max_products <= 0)
|
||
return;
|
||
|
||
if (r->terrain == newterrain(T_DESERT)
|
||
&& buildingtype_exists(r, caravan_bt, true)) {
|
||
max_products = rpeasants(r) * 2 / TRADE_FRACTION;
|
||
}
|
||
/* Verkauf: so programmiert, dass er leicht auf mehrere Gueter pro
|
||
* Runde erweitert werden kann. */
|
||
|
||
expandorders(r, sellorders);
|
||
if (!norders)
|
||
return;
|
||
|
||
for (j = 0; j != norders; j++) {
|
||
const luxury_type *search = NULL;
|
||
const luxury_type *ltype = oa[j].type.ltype;
|
||
int multi = r_demand(r, ltype);
|
||
int i;
|
||
int use = 0;
|
||
for (i = 0, search = luxurytypes; search != ltype; search = search->next) {
|
||
// TODO: this is slow and lame!
|
||
++i;
|
||
}
|
||
if (counter[i] >= limit)
|
||
continue;
|
||
if (counter[i] + 1 > max_products && multi > 1)
|
||
--multi;
|
||
price = ltype->price * multi;
|
||
|
||
if (money >= price) {
|
||
int abgezogenhafen = 0;
|
||
int abgezogensteuer = 0;
|
||
unit *u = oa[j].unit;
|
||
item *itm;
|
||
attrib *a = a_find(u->attribs, &at_luxuries);
|
||
if (a == NULL)
|
||
a = a_add(&u->attribs, a_new(&at_luxuries));
|
||
itm = (item *)a->data.v;
|
||
i_change(&itm, ltype->itype, 1);
|
||
a->data.v = itm;
|
||
++use;
|
||
if (u->n < 0)
|
||
u->n = 0;
|
||
|
||
if (hafenowner != NULL) {
|
||
if (hafenowner->faction != u->faction) {
|
||
abgezogenhafen = price / 10;
|
||
hafencollected += abgezogenhafen;
|
||
price -= abgezogenhafen;
|
||
money -= abgezogenhafen;
|
||
}
|
||
}
|
||
if (maxb != NULL) {
|
||
if (maxowner->faction != u->faction) {
|
||
abgezogensteuer = price * tax_per_size[maxeffsize] / 100;
|
||
taxcollected += abgezogensteuer;
|
||
price -= abgezogensteuer;
|
||
money -= abgezogensteuer;
|
||
}
|
||
}
|
||
u->n += price;
|
||
change_money(u, price);
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
|
||
/* r->money -= price; --- dies wird eben nicht ausgef<65>hrt, denn die
|
||
* Produkte k<>nnen auch als Steuern eingetrieben werden. In der Region
|
||
* wurden Silberst<73>cke gegen Luxusg<73>ter des selben Wertes eingetauscht!
|
||
* Falls mehr als max_products Kunden ein Produkt gekauft haben, sinkt
|
||
* die Nachfrage f<>r das Produkt um 1. Der Z<>hler wird wieder auf 0
|
||
* gesetzt. */
|
||
|
||
if (++counter[i] > max_products) {
|
||
int d = r_demand(r, ltype);
|
||
if (d > 1) {
|
||
r_setdemand(r, ltype, d - 1);
|
||
}
|
||
counter[i] = 0;
|
||
}
|
||
}
|
||
if (use > 0) {
|
||
use_pooled(oa[j].unit, ltype->itype->rtype, GET_DEFAULT, use);
|
||
}
|
||
}
|
||
free(oa);
|
||
|
||
/* Steuern. Hier werden die Steuern dem Besitzer der gr<67><72>ten Burg gegeben. */
|
||
if (maxowner) {
|
||
if (taxcollected > 0) {
|
||
change_money(maxowner, (int)taxcollected);
|
||
add_income(maxowner, IC_TRADETAX, taxcollected, taxcollected);
|
||
/* TODO: Meldung
|
||
* "%s verdient %d Silber durch den Handel in %s.",
|
||
* unitname(maxowner), (int) taxcollected, regionname(r)); */
|
||
}
|
||
}
|
||
if (hafenowner) {
|
||
if (hafencollected > 0) {
|
||
change_money(hafenowner, (int)hafencollected);
|
||
add_income(hafenowner, IC_TRADETAX, hafencollected, hafencollected);
|
||
}
|
||
}
|
||
/* Berichte an die Einheiten */
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
|
||
attrib *a = a_find(u->attribs, &at_luxuries);
|
||
item *itm;
|
||
if (a == NULL)
|
||
continue;
|
||
for (itm = (item *)a->data.v; itm; itm = itm->next) {
|
||
if (itm->number) {
|
||
ADDMSG(&u->faction->msgs, msg_message("sellamount",
|
||
"unit amount resource", u, itm->number, itm->type->rtype));
|
||
}
|
||
}
|
||
a_remove(&u->attribs, a);
|
||
add_income(u, IC_TRADE, u->n, u->n);
|
||
}
|
||
}
|
||
|
||
static bool sell(unit * u, request ** sellorders, struct order *ord)
|
||
{
|
||
char token[128];
|
||
bool unlimited = true;
|
||
const item_type *itype;
|
||
const luxury_type *ltype;
|
||
int n;
|
||
region *r = u->region;
|
||
const char *s;
|
||
keyword_t kwd;
|
||
static int bt_cache;
|
||
static const struct building_type *castle_bt, *caravan_bt;
|
||
|
||
if (bt_changed(&bt_cache)) {
|
||
castle_bt = bt_find("castle");
|
||
caravan_bt = bt_find("caravan");
|
||
}
|
||
|
||
if (u->ship && is_guarded(r, u, GUARD_CREWS)) {
|
||
cmistake(u, ord, 69, MSG_INCOME);
|
||
return false;
|
||
}
|
||
/* sellorders sind KEIN array, weil f<>r alle items DIE SELBE resource
|
||
* (das geld der region) aufgebraucht wird. */
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_SELL);
|
||
s = gettoken(token, sizeof(token));
|
||
|
||
if (findparam(s, u->faction->locale) == P_ANY) {
|
||
unlimited = false;
|
||
n = rpeasants(r) / TRADE_FRACTION;
|
||
if (r->terrain == newterrain(T_DESERT)
|
||
&& buildingtype_exists(r, caravan_bt, true))
|
||
n *= 2;
|
||
if (n == 0) {
|
||
cmistake(u, ord, 303, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
}
|
||
else {
|
||
n = s ? atoi(s) : 0;
|
||
if (n == 0) {
|
||
cmistake(u, ord, 27, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
}
|
||
/* Belagerte Einheiten k<>nnen nichts verkaufen. */
|
||
|
||
if (besieged(u)) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error60", ""));
|
||
return false;
|
||
}
|
||
/* In der Region mu<6D> es eine Burg geben. */
|
||
|
||
if (u_race(u) == get_race(RC_INSECT)) {
|
||
if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT)
|
||
&& !rbuildings(r)) {
|
||
cmistake(u, ord, 119, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
}
|
||
else {
|
||
/* ...oder in der Region mu<6D> es eine Burg geben. */
|
||
building *b = 0;
|
||
if (r->buildings) {
|
||
for (b = r->buildings; b; b = b->next) {
|
||
if (b->type == castle_bt && b->size >= 2) break;
|
||
}
|
||
}
|
||
if (!b) {
|
||
cmistake(u, ord, 119, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/* Ein H<>ndler kann nur 10 G<>ter pro Talentpunkt verkaufen. */
|
||
|
||
n = _min(n, u->number * 10 * effskill(u, SK_TRADE, 0));
|
||
|
||
if (!n) {
|
||
cmistake(u, ord, 54, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
s = gettoken(token, sizeof(token));
|
||
itype = s ? finditemtype(s, u->faction->locale) : 0;
|
||
ltype = itype ? resource2luxury(itype->rtype) : 0;
|
||
if (ltype == NULL) {
|
||
cmistake(u, ord, 126, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
else {
|
||
attrib *a;
|
||
request *o;
|
||
int k, available;
|
||
|
||
if (!r_demand(r, ltype)) {
|
||
cmistake(u, ord, 263, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
available = get_pooled(u, itype->rtype, GET_DEFAULT, INT_MAX);
|
||
|
||
/* Wenn andere Einheiten das selbe verkaufen, mu<6D> ihr Zeug abgezogen
|
||
* werden damit es nicht zweimal verkauft wird: */
|
||
for (o = *sellorders; o; o = o->next) {
|
||
if (o->type.ltype == ltype && o->unit->faction == u->faction) {
|
||
int fpool =
|
||
o->qty - get_pooled(o->unit, itype->rtype, GET_RESERVE, INT_MAX);
|
||
available -= _max(0, fpool);
|
||
}
|
||
}
|
||
|
||
n = _min(n, available);
|
||
|
||
if (n <= 0) {
|
||
cmistake(u, ord, 264, MSG_COMMERCE);
|
||
return false;
|
||
}
|
||
/* Hier wird request->type verwendet, weil die obere limit durch
|
||
* das silber gegeben wird (region->money), welches f<>r alle
|
||
* (!) produkte als summe gilt, als nicht wie bei der
|
||
* produktion, wo f<>r jedes produkt einzeln eine obere limite
|
||
* existiert, so dass man arrays von orders machen kann. */
|
||
|
||
/* Ein H<>ndler kann nur 10 G<>ter pro Talentpunkt handeln. */
|
||
k = u->number * 10 * effskill(u, SK_TRADE, 0);
|
||
|
||
/* hat der H<>ndler bereits gehandelt, muss die Menge der bereits
|
||
* verkauften/gekauften G<>ter abgezogen werden */
|
||
a = a_find(u->attribs, &at_trades);
|
||
if (!a) {
|
||
a = a_add(&u->attribs, a_new(&at_trades));
|
||
}
|
||
else {
|
||
k -= a->data.i;
|
||
}
|
||
|
||
n = _min(n, k);
|
||
assert(n >= 0);
|
||
/* die Menge der verkauften G<>ter merken */
|
||
a->data.i += n;
|
||
o = (request *)calloc(1, sizeof(request));
|
||
o->unit = u;
|
||
o->qty = n;
|
||
o->type.ltype = ltype;
|
||
addlist(sellorders, o);
|
||
|
||
return unlimited;
|
||
}
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
|
||
static void expandstealing(region * r, request * stealorders)
|
||
{
|
||
const resource_type *rsilver = get_resourcetype(R_SILVER);
|
||
unsigned int j;
|
||
|
||
assert(rsilver);
|
||
|
||
expandorders(r, stealorders);
|
||
if (!norders) return;
|
||
|
||
/* F<>r jede unit in der Region wird Geld geklaut, wenn sie Opfer eines
|
||
* Beklauen-Orders ist. Jedes Opfer mu<6D> einzeln behandelt werden.
|
||
*
|
||
* u ist die beklaute unit. oa.unit ist die klauende unit.
|
||
*/
|
||
|
||
for (j = 0; j != norders && oa[j].unit->n <= oa[j].unit->wants; j++) {
|
||
unit *u = findunitg(oa[j].no, r);
|
||
int n = 0;
|
||
if (u && u->region == r) {
|
||
n = get_pooled(u, rsilver, GET_ALL, INT_MAX);
|
||
}
|
||
#ifndef GOBLINKILL
|
||
if (oa[i].type.goblin) { /* Goblin-Spezialklau */
|
||
int uct = 0;
|
||
unit *u2;
|
||
assert(effskill(oa[i].unit, SK_STEALTH) >= 4
|
||
|| !"this goblin\'s skill is too low");
|
||
for (u2 = r->units; u2; u2 = u2->next) {
|
||
if (u2->faction == u->faction) {
|
||
uct += maintenance_cost(u2);
|
||
}
|
||
}
|
||
n -= uct * 2;
|
||
}
|
||
#endif
|
||
if (n > 10 && rplane(r) && (rplane(r)->flags & PFL_NOALLIANCES)) {
|
||
/* In Questen nur reduziertes Klauen */
|
||
n = 10;
|
||
}
|
||
if (n > 0) {
|
||
n = _min(n, oa[j].unit->wants);
|
||
use_pooled(u, rsilver, GET_ALL, n);
|
||
oa[j].unit->n = n;
|
||
change_money(oa[j].unit, n);
|
||
ADDMSG(&u->faction->msgs, msg_message("stealeffect", "unit region amount",
|
||
u, u->region, n));
|
||
}
|
||
add_income(oa[j].unit, IC_STEAL, oa[j].unit->wants, oa[j].unit->n);
|
||
fset(oa[j].unit, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
free(oa);
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
static void plant(unit * u, int raw)
|
||
{
|
||
int n, i, skill, planted = 0;
|
||
const item_type *itype;
|
||
const resource_type *rt_water = get_resourcetype(R_WATER_OF_LIFE);
|
||
region *r = u->region;
|
||
|
||
assert(rt_water != NULL);
|
||
if (!fval(r->terrain, LAND_REGION)) {
|
||
return;
|
||
}
|
||
if (rherbtype(r) == NULL) {
|
||
cmistake(u, u->thisorder, 108, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
|
||
/* Skill pr<70>fen */
|
||
skill = effskill(u, SK_HERBALISM, 0);
|
||
itype = rherbtype(r);
|
||
if (skill < 6) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "plant_skills",
|
||
"skill minskill product", SK_HERBALISM, 6, itype->rtype, 1));
|
||
return;
|
||
}
|
||
/* Wasser des Lebens pr<70>fen */
|
||
if (get_pooled(u, rt_water, GET_DEFAULT, 1) == 0) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "resource_missing", "missing", rt_water));
|
||
return;
|
||
}
|
||
n = get_pooled(u, itype->rtype, GET_DEFAULT, skill * u->number);
|
||
/* Kr<4B>uter pr<70>fen */
|
||
if (n == 0) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "resource_missing", "missing",
|
||
itype->rtype));
|
||
return;
|
||
}
|
||
|
||
n = _min(skill * u->number, n);
|
||
n = _min(raw, n);
|
||
/* F<>r jedes Kraut Talent*10% Erfolgschance. */
|
||
for (i = n; i > 0; i--) {
|
||
if (rng_int() % 10 < skill)
|
||
planted++;
|
||
}
|
||
produceexp(u, SK_HERBALISM, u->number);
|
||
|
||
/* Alles ok. Abziehen. */
|
||
use_pooled(u, rt_water, GET_DEFAULT, 1);
|
||
use_pooled(u, itype->rtype, GET_DEFAULT, n);
|
||
rsetherbs(r, (short)(rherbs(r) + planted));
|
||
ADDMSG(&u->faction->msgs, msg_message("plant", "unit region amount herb",
|
||
u, r, planted, itype->rtype));
|
||
}
|
||
|
||
static void planttrees(unit * u, int raw)
|
||
{
|
||
int n, i, skill, planted = 0;
|
||
const resource_type *rtype;
|
||
region * r = u->region;
|
||
|
||
if (!fval(r->terrain, LAND_REGION)) {
|
||
return;
|
||
}
|
||
|
||
/* Mallornb<6E>ume kann man nur in Mallornregionen z<>chten */
|
||
rtype = get_resourcetype(fval(r, RF_MALLORN) ? R_MALLORNSEED : R_SEED);
|
||
|
||
/* Skill pr<70>fen */
|
||
skill = effskill(u, SK_HERBALISM, 0);
|
||
if (skill < 6) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "plant_skills",
|
||
"skill minskill product", SK_HERBALISM, 6, rtype, 1));
|
||
return;
|
||
}
|
||
if (fval(r, RF_MALLORN) && skill < 7) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "plant_skills",
|
||
"skill minskill product", SK_HERBALISM, 7, rtype, 1));
|
||
return;
|
||
}
|
||
|
||
/* wenn eine Anzahl angegeben wurde, nur soviel verbrauchen */
|
||
raw = _min(raw, skill * u->number);
|
||
n = get_pooled(u, rtype, GET_DEFAULT, raw);
|
||
if (n == 0) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "resource_missing", "missing", rtype));
|
||
return;
|
||
}
|
||
n = _min(raw, n);
|
||
|
||
/* F<>r jeden Samen Talent*10% Erfolgschance. */
|
||
for (i = n; i > 0; i--) {
|
||
if (rng_int() % 10 < skill)
|
||
planted++;
|
||
}
|
||
rsettrees(r, 0, rtrees(r, 0) + planted);
|
||
|
||
/* Alles ok. Abziehen. */
|
||
produceexp(u, SK_HERBALISM, u->number);
|
||
use_pooled(u, rtype, GET_DEFAULT, n);
|
||
|
||
ADDMSG(&u->faction->msgs, msg_message("plant",
|
||
"unit region amount herb", u, r, planted, rtype));
|
||
}
|
||
|
||
/* z<>chte b<>ume */
|
||
static void breedtrees(unit * u, int raw)
|
||
{
|
||
int n, i, skill, planted = 0;
|
||
const resource_type *rtype;
|
||
int current_season;
|
||
region *r = u->region;
|
||
gamedate date;
|
||
|
||
get_gamedate(turn, &date);
|
||
current_season = date.season;
|
||
|
||
/* B<>ume z<>chten geht nur im Fr<46>hling */
|
||
if (current_season != SEASON_SPRING) {
|
||
planttrees(u, raw);
|
||
return;
|
||
}
|
||
|
||
if (!fval(r->terrain, LAND_REGION)) {
|
||
return;
|
||
}
|
||
|
||
/* Mallornb<6E>ume kann man nur in Mallornregionen z<>chten */
|
||
rtype = get_resourcetype(fval(r, RF_MALLORN) ? R_MALLORNSEED : R_SEED);
|
||
|
||
/* Skill pr<70>fen */
|
||
skill = effskill(u, SK_HERBALISM, 0);
|
||
if (skill < 12) {
|
||
planttrees(u, raw);
|
||
return;
|
||
}
|
||
|
||
/* wenn eine Anzahl angegeben wurde, nur soviel verbrauchen */
|
||
raw = _min(skill * u->number, raw);
|
||
n = get_pooled(u, rtype, GET_DEFAULT, raw);
|
||
/* Samen pr<70>fen */
|
||
if (n == 0) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, u->thisorder, "resource_missing", "missing", rtype));
|
||
return;
|
||
}
|
||
n = _min(raw, n);
|
||
|
||
/* F<>r jeden Samen Talent*5% Erfolgschance. */
|
||
for (i = n; i > 0; i--) {
|
||
if (rng_int() % 100 < skill * 5)
|
||
planted++;
|
||
}
|
||
rsettrees(r, 1, rtrees(r, 1) + planted);
|
||
|
||
/* Alles ok. Abziehen. */
|
||
produceexp(u, SK_HERBALISM, u->number);
|
||
use_pooled(u, rtype, GET_DEFAULT, n);
|
||
|
||
ADDMSG(&u->faction->msgs, msg_message("plant",
|
||
"unit region amount herb", u, r, planted, rtype));
|
||
}
|
||
|
||
/* z<>chte pferde */
|
||
static void breedhorses(unit * u)
|
||
{
|
||
int n, c, breed = 0;
|
||
const struct resource_type *rhorse = get_resourcetype(R_HORSE);
|
||
int horses, effsk;
|
||
static int bt_cache;
|
||
static const struct building_type *stables_bt;
|
||
|
||
if (bt_changed(&bt_cache)) {
|
||
stables_bt = bt_find("stables");
|
||
}
|
||
|
||
assert(rhorse && rhorse->itype);
|
||
if (!active_building(u, stables_bt)) {
|
||
cmistake(u, u->thisorder, 122, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
horses = i_get(u->items, rhorse->itype);
|
||
if (horses < 2) {
|
||
cmistake(u, u->thisorder, 107, MSG_PRODUCE);
|
||
return;
|
||
}
|
||
effsk = effskill(u, SK_HORSE_TRAINING, 0);
|
||
n = u->number * effsk;
|
||
n = _min(n, horses);
|
||
|
||
for (c = 0; c < n; c++) {
|
||
if (rng_int() % 100 < effsk) {
|
||
i_change(&u->items, rhorse->itype, 1);
|
||
++breed;
|
||
}
|
||
}
|
||
|
||
produceexp(u, SK_HORSE_TRAINING, u->number);
|
||
|
||
ADDMSG(&u->faction->msgs, msg_message("raised", "unit amount", u, breed));
|
||
}
|
||
|
||
static void breed_cmd(unit * u, struct order *ord)
|
||
{
|
||
char token[128];
|
||
int m;
|
||
const char *s;
|
||
param_t p;
|
||
region *r = u->region;
|
||
const resource_type *rtype = NULL;
|
||
|
||
if (r->land == NULL) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_onlandonly", ""));
|
||
return;
|
||
}
|
||
|
||
/* z<>chte [<anzahl>] <parameter> */
|
||
(void)init_order(ord);
|
||
s = gettoken(token, sizeof(token));
|
||
|
||
m = s ? atoi((const char *)s) : 0;
|
||
if (m != 0) {
|
||
/* first came a want-paramter */
|
||
s = gettoken(token, sizeof(token));
|
||
}
|
||
else {
|
||
m = INT_MAX;
|
||
}
|
||
|
||
if (!s || !s[0]) {
|
||
p = P_ANY;
|
||
}
|
||
else {
|
||
p = findparam(s, u->faction->locale);
|
||
}
|
||
|
||
switch (p) {
|
||
case P_HERBS:
|
||
plant(u, m);
|
||
break;
|
||
case P_TREES:
|
||
breedtrees(u, m);
|
||
break;
|
||
default:
|
||
if (p != P_ANY) {
|
||
rtype = findresourcetype(s, u->faction->locale);
|
||
if (rtype == get_resourcetype(R_SEED) || rtype == get_resourcetype(R_MALLORNSEED)) {
|
||
breedtrees(u, m);
|
||
break;
|
||
}
|
||
else if (rtype != get_resourcetype(R_HORSE)) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_cannotmake", ""));
|
||
break;
|
||
}
|
||
}
|
||
breedhorses(u);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static const char *rough_amount(int a, int m)
|
||
{
|
||
int p = (a * 100) / m;
|
||
|
||
if (p < 10) {
|
||
return "sehr wenige";
|
||
}
|
||
else if (p < 30) {
|
||
return "wenige";
|
||
}
|
||
else if (p < 60) {
|
||
return "relativ viele";
|
||
}
|
||
else if (p < 90) {
|
||
return "viele";
|
||
}
|
||
return "sehr viele";
|
||
}
|
||
|
||
static void research_cmd(unit * u, struct order *ord)
|
||
{
|
||
region *r = u->region;
|
||
keyword_t kwd;
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_RESEARCH);
|
||
|
||
if (effskill(u, SK_HERBALISM, 0) < 7) {
|
||
cmistake(u, ord, 227, MSG_EVENT);
|
||
return;
|
||
}
|
||
|
||
produceexp(u, SK_HERBALISM, u->number);
|
||
|
||
if (rherbs(r) > 0) {
|
||
const item_type *itype = rherbtype(r);
|
||
|
||
if (itype != NULL) {
|
||
ADDMSG(&u->faction->msgs, msg_message("researchherb",
|
||
"unit region amount herb",
|
||
u, r, rough_amount(rherbs(r), 100), itype->rtype));
|
||
}
|
||
else {
|
||
ADDMSG(&u->faction->msgs, msg_message("researchherb_none",
|
||
"unit region", u, r));
|
||
}
|
||
}
|
||
else {
|
||
ADDMSG(&u->faction->msgs, msg_message("researchherb_none",
|
||
"unit region", u, r));
|
||
}
|
||
}
|
||
|
||
static int max_skill(region * r, faction * f, skill_t sk)
|
||
{
|
||
unit *u;
|
||
int w = 0;
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
if (u->faction == f) {
|
||
int effsk = effskill(u, sk, 0);
|
||
if (effsk > w) {
|
||
w = effsk;
|
||
}
|
||
}
|
||
}
|
||
|
||
return w;
|
||
}
|
||
|
||
message * check_steal(const unit * u, struct order *ord) {
|
||
plane *pl;
|
||
|
||
if (fval(u_race(u), RCF_NOSTEAL)) {
|
||
return msg_feedback(u, ord, "race_nosteal", "race", u_race(u));
|
||
}
|
||
|
||
if (fval(u->region->terrain, SEA_REGION) && u_race(u) != get_race(RC_AQUARIAN)) {
|
||
return msg_feedback(u, ord, "error_onlandonly", "");
|
||
}
|
||
|
||
pl = rplane(u->region);
|
||
if (pl && fval(pl, PFL_NOATTACK)) {
|
||
return msg_feedback(u, ord, "error270", "");
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void steal_cmd(unit * u, struct order *ord, request ** stealorders)
|
||
{
|
||
const resource_type *rring = get_resourcetype(R_RING_OF_NIMBLEFINGER);
|
||
int n, i, id, effsk;
|
||
bool goblin = false;
|
||
request *o;
|
||
unit *u2 = NULL;
|
||
region *r = u->region;
|
||
faction *f = NULL;
|
||
message * msg;
|
||
keyword_t kwd;
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_STEAL);
|
||
|
||
assert(skill_enabled(SK_PERCEPTION) && skill_enabled(SK_STEALTH));
|
||
|
||
msg = check_steal(u, ord);
|
||
if (msg) {
|
||
ADDMSG(&u->faction->msgs, msg);
|
||
return;
|
||
}
|
||
id = read_unitid(u->faction, r);
|
||
if (id > 0) {
|
||
u2 = findunitr(r, id);
|
||
}
|
||
if (u2 && u2->region == u->region) {
|
||
f = u2->faction;
|
||
}
|
||
else {
|
||
// TODO: is this really necessary? it's the only time we use faction.c/deadhash
|
||
// it allows stealing from a unit in a dead faction, but why?
|
||
f = dfindhash(id);
|
||
}
|
||
|
||
for (u2 = r->units; u2; u2 = u2->next) {
|
||
if (u2->faction == f && cansee(u->faction, r, u2, 0))
|
||
break;
|
||
}
|
||
|
||
if (!u2) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found",
|
||
""));
|
||
return;
|
||
}
|
||
|
||
if (IsImmune(u2->faction)) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, ord, "newbie_immunity_error", "turns", NewbieImmunity()));
|
||
return;
|
||
}
|
||
|
||
if (u->faction->alliance && u->faction->alliance == u2->faction->alliance) {
|
||
cmistake(u, ord, 47, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
assert(u->region == u2->region);
|
||
if (!can_contact(r, u, u2)) {
|
||
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error60", ""));
|
||
return;
|
||
}
|
||
|
||
effsk = effskill(u, SK_STEALTH, 0);
|
||
n = effsk - max_skill(r, f, SK_PERCEPTION);
|
||
|
||
if (n <= 0) {
|
||
/* Wahrnehmung == Tarnung */
|
||
if (u_race(u) != get_race(RC_GOBLIN) || effsk <= 3) {
|
||
ADDMSG(&u->faction->msgs, msg_message("stealfail", "unit target", u, u2));
|
||
if (n == 0) {
|
||
ADDMSG(&u2->faction->msgs, msg_message("stealdetect", "unit", u2));
|
||
}
|
||
else {
|
||
ADDMSG(&u2->faction->msgs, msg_message("thiefdiscover", "unit target",
|
||
u, u2));
|
||
}
|
||
return;
|
||
}
|
||
else {
|
||
ADDMSG(&u->faction->msgs, msg_message("stealfatal", "unit target", u,
|
||
u2));
|
||
ADDMSG(&u2->faction->msgs, msg_message("thiefdiscover", "unit target", u,
|
||
u2));
|
||
n = 1;
|
||
goblin = true;
|
||
}
|
||
}
|
||
|
||
i = _min(u->number, i_get(u->items, rring->itype));
|
||
if (i > 0) {
|
||
n *= STEALINCOME * (u->number + i * (roqf_factor() - 1));
|
||
}
|
||
else {
|
||
n *= u->number * STEALINCOME;
|
||
}
|
||
|
||
u->wants = n;
|
||
|
||
/* wer dank unsichtbarkeitsringen klauen kann, muss nicht unbedingt ein
|
||
* guter dieb sein, schliesslich macht man immer noch sehr viel laerm */
|
||
|
||
o = (request *)calloc(1, sizeof(request));
|
||
o->unit = u;
|
||
o->qty = 1; /* Betrag steht in u->wants */
|
||
o->no = u2->no;
|
||
o->type.goblin = goblin; /* Merken, wenn Goblin-Spezialklau */
|
||
addlist(stealorders, o);
|
||
|
||
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
|
||
|
||
produceexp(u, SK_STEALTH, _min(n, u->number));
|
||
}
|
||
|
||
/* ------------------------------------------------------------- */
|
||
|
||
static void expandentertainment(region * r)
|
||
{
|
||
unit *u;
|
||
int m = entertainmoney(r);
|
||
request *o;
|
||
|
||
for (o = &entertainers[0]; o != nextentertainer; ++o) {
|
||
double part = m / (double)entertaining;
|
||
u = o->unit;
|
||
if (entertaining <= m)
|
||
u->n = o->qty;
|
||
else
|
||
u->n = (int)(o->qty * part);
|
||
change_money(u, u->n);
|
||
rsetmoney(r, rmoney(r) - u->n);
|
||
m -= u->n;
|
||
entertaining -= o->qty;
|
||
|
||
/* Nur soviel PRODUCEEXP wie auch tats<74>chlich gemacht wurde */
|
||
produceexp(u, SK_ENTERTAINMENT, _min(u->n, u->number));
|
||
add_income(u, IC_ENTERTAIN, o->qty, u->n);
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
}
|
||
|
||
void entertain_cmd(unit * u, struct order *ord)
|
||
{
|
||
region *r = u->region;
|
||
int max_e;
|
||
request *o;
|
||
static int entertainbase = 0;
|
||
static int entertainperlevel = 0;
|
||
keyword_t kwd;
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_ENTERTAIN);
|
||
if (!entertainbase) {
|
||
const char *str = config_get("entertain.base");
|
||
entertainbase = str ? atoi(str) : 0;
|
||
}
|
||
if (!entertainperlevel) {
|
||
const char *str = config_get("entertain.perlevel");
|
||
entertainperlevel = str ? atoi(str) : 0;
|
||
}
|
||
if (fval(u, UFL_WERE)) {
|
||
cmistake(u, ord, 58, MSG_INCOME);
|
||
return;
|
||
}
|
||
if (!effskill(u, SK_ENTERTAINMENT, 0)) {
|
||
cmistake(u, ord, 58, MSG_INCOME);
|
||
return;
|
||
}
|
||
if (besieged(u)) {
|
||
cmistake(u, ord, 60, MSG_INCOME);
|
||
return;
|
||
}
|
||
if (u->ship && is_guarded(r, u, GUARD_CREWS)) {
|
||
cmistake(u, ord, 69, MSG_INCOME);
|
||
return;
|
||
}
|
||
if (is_cursed(r->attribs, C_DEPRESSION, 0)) {
|
||
cmistake(u, ord, 28, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
u->wants = u->number * (entertainbase + effskill(u, SK_ENTERTAINMENT, 0)
|
||
* entertainperlevel);
|
||
|
||
max_e = getuint();
|
||
if (max_e != 0) {
|
||
u->wants = _min(u->wants, max_e);
|
||
}
|
||
o = nextentertainer++;
|
||
o->unit = u;
|
||
o->qty = u->wants;
|
||
entertaining += o->qty;
|
||
}
|
||
|
||
/**
|
||
* \return number of working spaces taken by players
|
||
*/
|
||
static void
|
||
expandwork(region * r, request * work_begin, request * work_end, int maxwork)
|
||
{
|
||
int earnings;
|
||
/* n: verbleibende Einnahmen */
|
||
/* fishes: maximale Arbeiter */
|
||
int jobs = maxwork;
|
||
int p_wage = wage(r, NULL, NULL, turn);
|
||
int money = rmoney(r);
|
||
request *o;
|
||
|
||
for (o = work_begin; o != work_end; ++o) {
|
||
unit *u = o->unit;
|
||
int workers;
|
||
|
||
if (u->number == 0)
|
||
continue;
|
||
|
||
if (jobs >= working)
|
||
workers = u->number;
|
||
else {
|
||
workers = u->number * jobs / working;
|
||
if (rng_int() % working < (u->number * jobs) % working)
|
||
workers++;
|
||
}
|
||
|
||
assert(workers >= 0);
|
||
|
||
u->n = workers * wage(u->region, u->faction, u_race(u), turn);
|
||
|
||
jobs -= workers;
|
||
assert(jobs >= 0);
|
||
|
||
change_money(u, u->n);
|
||
working -= o->unit->number;
|
||
add_income(u, IC_WORK, o->qty, u->n);
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
|
||
if (jobs > rpeasants(r)) {
|
||
jobs = rpeasants(r);
|
||
}
|
||
earnings = jobs * p_wage;
|
||
if (r->attribs && rule_blessed_harvest() == HARVEST_TAXES) {
|
||
/* E3 rules */
|
||
const curse_type *blessedharvest_ct = ct_find("blessedharvest");
|
||
if (blessedharvest_ct) {
|
||
int happy =
|
||
(int)curse_geteffect(get_curse(r->attribs, blessedharvest_ct));
|
||
happy = _min(happy, jobs);
|
||
earnings += happy;
|
||
}
|
||
}
|
||
rsetmoney(r, money + earnings);
|
||
}
|
||
|
||
static int do_work(unit * u, order * ord, request * o)
|
||
{
|
||
if (playerrace(u_race(u))) {
|
||
region *r = u->region;
|
||
int w;
|
||
|
||
if (fval(u, UFL_WERE)) {
|
||
if (ord)
|
||
cmistake(u, ord, 313, MSG_INCOME);
|
||
return -1;
|
||
}
|
||
if (besieged(u)) {
|
||
if (ord)
|
||
cmistake(u, ord, 60, MSG_INCOME);
|
||
return -1;
|
||
}
|
||
if (u->ship && is_guarded(r, u, GUARD_CREWS)) {
|
||
if (ord)
|
||
cmistake(u, ord, 69, MSG_INCOME);
|
||
return -1;
|
||
}
|
||
w = wage(r, u->faction, u_race(u), turn);
|
||
u->wants = u->number * w;
|
||
o->unit = u;
|
||
o->qty = u->number * w;
|
||
working += u->number;
|
||
return 0;
|
||
}
|
||
else if (ord && !is_monsters(u->faction)) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, ord, "race_cantwork", "race", u_race(u)));
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static void expandloot(region * r, request * lootorders)
|
||
{
|
||
unit *u;
|
||
unsigned int i;
|
||
int looted = 0;
|
||
int startmoney = rmoney(r);
|
||
|
||
expandorders(r, lootorders);
|
||
if (!norders)
|
||
return;
|
||
|
||
for (i = 0; i != norders && startmoney > looted + TAXFRACTION * 2; i++) {
|
||
change_money(oa[i].unit, TAXFRACTION);
|
||
oa[i].unit->n += TAXFRACTION;
|
||
/*Looting destroys double the money*/
|
||
looted += TAXFRACTION * 2;
|
||
}
|
||
rsetmoney(r, startmoney - looted);
|
||
free(oa);
|
||
|
||
/* Lowering morale by 1 depending on the looted money (+20%) */
|
||
if (rng_int() % 100 < 20 + (looted * 80) / startmoney) {
|
||
int m = region_get_morale(r);
|
||
if (m) {
|
||
/*Nur Moral -1, turns is not changed, so the first time nothing happens if the morale is good*/
|
||
region_set_morale(r, m - 1, -1);
|
||
}
|
||
}
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
if (u->n >= 0) {
|
||
add_income(u, IC_LOOT, u->wants, u->n);
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
}
|
||
}
|
||
|
||
void expandtax(region * r, request * taxorders)
|
||
{
|
||
unit *u;
|
||
unsigned int i;
|
||
|
||
expandorders(r, taxorders);
|
||
if (!norders)
|
||
return;
|
||
|
||
for (i = 0; i != norders && rmoney(r) > TAXFRACTION; i++) {
|
||
change_money(oa[i].unit, TAXFRACTION);
|
||
oa[i].unit->n += TAXFRACTION;
|
||
rsetmoney(r, rmoney(r) - TAXFRACTION);
|
||
}
|
||
free(oa);
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
if (u->n >= 0) {
|
||
add_income(u, IC_TAX, u->wants, u->n);
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
}
|
||
}
|
||
}
|
||
|
||
void tax_cmd(unit * u, struct order *ord, request ** taxorders)
|
||
{
|
||
/* Steuern werden noch vor der Forschung eingetrieben */
|
||
region *r = u->region;
|
||
unit *u2;
|
||
int n;
|
||
request *o;
|
||
int max;
|
||
keyword_t kwd;
|
||
static int taxperlevel = 0;
|
||
|
||
if (!taxperlevel) {
|
||
taxperlevel = config_get_int("taxing.perlevel", 0);
|
||
}
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_TAX);
|
||
|
||
if (!humanoidrace(u_race(u)) && !is_monsters(u->faction)) {
|
||
cmistake(u, ord, 228, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
if (fval(u, UFL_WERE)) {
|
||
cmistake(u, ord, 228, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
if (besieged(u)) {
|
||
cmistake(u, ord, 60, MSG_INCOME);
|
||
return;
|
||
}
|
||
n = armedmen(u, false);
|
||
|
||
if (!n) {
|
||
cmistake(u, ord, 48, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
if (effskill(u, SK_TAXING, 0) <= 0) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, ord, "error_no_tax_skill", ""));
|
||
return;
|
||
}
|
||
|
||
max = getint();
|
||
|
||
if (max <= 0) {
|
||
max = INT_MAX;
|
||
}
|
||
if (!playerrace(u_race(u))) {
|
||
u->wants = _min(income(u), max);
|
||
}
|
||
else {
|
||
u->wants = _min(n * effskill(u, SK_TAXING, 0) * taxperlevel, max);
|
||
}
|
||
|
||
u2 = is_guarded(r, u, GUARD_TAX);
|
||
if (u2) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, ord, "region_guarded", "guard", u2));
|
||
return;
|
||
}
|
||
|
||
/* die einnahmen werden in fraktionen von 10 silber eingeteilt: diese
|
||
* fraktionen werden dann bei eintreiben unter allen eintreibenden
|
||
* einheiten aufgeteilt. */
|
||
|
||
o = (request *)calloc(1, sizeof(request));
|
||
o->qty = u->wants / TAXFRACTION;
|
||
o->unit = u;
|
||
addlist(taxorders, o);
|
||
return;
|
||
}
|
||
|
||
void loot_cmd(unit * u, struct order *ord, request ** lootorders)
|
||
{
|
||
region *r = u->region;
|
||
unit *u2;
|
||
int n;
|
||
int max;
|
||
request *o;
|
||
keyword_t kwd;
|
||
|
||
kwd = init_order(ord);
|
||
assert(kwd == K_LOOT);
|
||
|
||
if (config_get_int("rules.enable_loot", 0) == 0 && !is_monsters(u->faction)) {
|
||
return;
|
||
}
|
||
|
||
if (!humanoidrace(u_race(u)) && !is_monsters(u->faction)) {
|
||
cmistake(u, ord, 228, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
if (fval(u, UFL_WERE)) {
|
||
cmistake(u, ord, 228, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
if (besieged(u)) {
|
||
cmistake(u, ord, 60, MSG_INCOME);
|
||
return;
|
||
}
|
||
n = armedmen(u, false);
|
||
|
||
if (!n) {
|
||
cmistake(u, ord, 48, MSG_INCOME);
|
||
return;
|
||
}
|
||
|
||
u2 = is_guarded(r, u, GUARD_TAX);
|
||
if (u2) {
|
||
ADDMSG(&u->faction->msgs,
|
||
msg_feedback(u, ord, "region_guarded", "guard", u2));
|
||
return;
|
||
}
|
||
|
||
max = getint();
|
||
|
||
if (max <= 0) {
|
||
max = INT_MAX;
|
||
}
|
||
if (!playerrace(u_race(u))) {
|
||
u->wants = _min(income(u), max);
|
||
}
|
||
else {
|
||
/* For player start with 20 Silver +10 every 5 level of close combat skill*/
|
||
int skbonus = (_max(effskill(u, SK_MELEE, 0), effskill(u, SK_SPEAR, 0)) * 2 / 10) + 2;
|
||
u->wants = _min(n * skbonus * 10, max);
|
||
}
|
||
|
||
o = (request *)calloc(1, sizeof(request));
|
||
o->qty = u->wants / TAXFRACTION;
|
||
o->unit = u;
|
||
addlist(lootorders, o);
|
||
|
||
return;
|
||
}
|
||
|
||
#define MAX_WORKERS 2048
|
||
void auto_work(region * r)
|
||
{
|
||
request workers[MAX_WORKERS];
|
||
request *nextworker = workers;
|
||
unit *u;
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
if (!(u->flags & UFL_LONGACTION) && !is_monsters(u->faction)) {
|
||
if (do_work(u, NULL, nextworker) == 0) {
|
||
assert(nextworker - workers < MAX_WORKERS);
|
||
++nextworker;
|
||
}
|
||
}
|
||
}
|
||
if (nextworker != workers) {
|
||
expandwork(r, workers, nextworker, maxworkingpeasants(r));
|
||
}
|
||
}
|
||
|
||
static void peasant_taxes(region * r)
|
||
{
|
||
faction *f;
|
||
unit *u;
|
||
building *b;
|
||
int money;
|
||
int maxsize;
|
||
|
||
f = region_get_owner(r);
|
||
if (f == NULL || is_mourning(r, turn)) {
|
||
return;
|
||
}
|
||
money = rmoney(r);
|
||
if (money <= 0)
|
||
return;
|
||
|
||
b = largestbuilding(r, cmp_taxes, false);
|
||
if (b == NULL)
|
||
return;
|
||
|
||
u = building_owner(b);
|
||
if (u == NULL || u->faction != f)
|
||
return;
|
||
|
||
maxsize = buildingeffsize(b, false);
|
||
if (maxsize > 0) {
|
||
double taxfactor = money * b->type->taxes(b, maxsize);
|
||
double morale = money * region_get_morale(r) * MORALE_TAX_FACTOR;
|
||
if (taxfactor > morale)
|
||
taxfactor = morale;
|
||
if (taxfactor > 0) {
|
||
int taxmoney = (int)taxfactor;
|
||
change_money(u, taxmoney);
|
||
rsetmoney(r, money - taxmoney);
|
||
ADDMSG(&u->faction->msgs, msg_message("income_tax",
|
||
"unit region amount", u, r, taxmoney));
|
||
}
|
||
}
|
||
}
|
||
|
||
static bool rule_auto_taxation(void)
|
||
{
|
||
return config_get_int("rules.economy.taxation", 0) != 0;
|
||
}
|
||
|
||
static bool rule_autowork(void) {
|
||
return config_get_int("work.auto", 0) != 0;
|
||
}
|
||
|
||
void produce(struct region *r)
|
||
{
|
||
request workers[MAX_WORKERS];
|
||
request *taxorders, *lootorders, *sellorders, *stealorders, *buyorders;
|
||
unit *u;
|
||
bool limited = true;
|
||
request *nextworker = workers;
|
||
static int bt_cache;
|
||
static const struct building_type *caravan_bt;
|
||
static int rc_cache;
|
||
static const race *rc_spell, *rc_insect, *rc_aquarian;
|
||
|
||
if (bt_changed(&bt_cache)) {
|
||
caravan_bt = bt_find("caravan");
|
||
}
|
||
if (rc_changed(&rc_cache)) {
|
||
rc_spell = get_race(RC_SPELL);
|
||
rc_insect = get_race(RC_INSECT);
|
||
rc_aquarian = get_race(RC_AQUARIAN);
|
||
}
|
||
assert(r);
|
||
|
||
/* das sind alles befehle, die 30 tage brauchen, und die in thisorder
|
||
* stehen! von allen 30-tage befehlen wird einfach der letzte verwendet
|
||
* (dosetdefaults).
|
||
*
|
||
* kaufen vor einnahmequellen. da man in einer region dasselbe produkt
|
||
* nicht kaufen und verkaufen kann, ist die reihenfolge wegen der
|
||
* produkte egal. nicht so wegen dem geld.
|
||
*
|
||
* lehren vor lernen. */
|
||
|
||
assert(rmoney(r) >= 0);
|
||
assert(rpeasants(r) >= 0);
|
||
|
||
if (r->land && rule_auto_taxation()) {
|
||
/* new taxation rules, region owners make money based on morale and building */
|
||
peasant_taxes(r);
|
||
}
|
||
|
||
buyorders = 0;
|
||
sellorders = 0;
|
||
working = 0;
|
||
nextentertainer = &entertainers[0];
|
||
entertaining = 0;
|
||
taxorders = 0;
|
||
lootorders = 0;
|
||
stealorders = 0;
|
||
|
||
for (u = r->units; u; u = u->next) {
|
||
order *ord;
|
||
bool trader = false;
|
||
keyword_t todo;
|
||
|
||
if (u_race(u) == rc_spell || fval(u, UFL_LONGACTION))
|
||
continue;
|
||
|
||
if (u_race(u) == rc_insect && r_insectstalled(r) &&
|
||
!is_cursed(u->attribs, C_KAELTESCHUTZ, 0))
|
||
continue;
|
||
|
||
if (fval(u, UFL_LONGACTION) && u->thisorder == NULL) {
|
||
/* this message was already given in laws.c:update_long_order
|
||
cmistake(u, u->thisorder, 52, MSG_PRODUCE);
|
||
*/
|
||
continue;
|
||
}
|
||
|
||
for (ord = u->orders; ord; ord = ord->next) {
|
||
keyword_t kwd = getkeyword(ord);
|
||
if (kwd == K_BUY) {
|
||
buy(u, &buyorders, ord);
|
||
trader = true;
|
||
}
|
||
else if (kwd == K_SELL) {
|
||
/* sell returns true if the sale is not limited
|
||
* by the region limit */
|
||
limited &= !sell(u, &sellorders, ord);
|
||
trader = true;
|
||
}
|
||
}
|
||
if (trader) {
|
||
attrib *a = a_find(u->attribs, &at_trades);
|
||
if (a && a->data.i) {
|
||
produceexp(u, SK_TRADE, u->number);
|
||
}
|
||
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
|
||
continue;
|
||
}
|
||
|
||
todo = getkeyword(u->thisorder);
|
||
if (todo == NOKEYWORD)
|
||
continue;
|
||
|
||
if (fval(r->terrain, SEA_REGION) && u_race(u) != rc_aquarian
|
||
&& !(u_race(u)->flags & RCF_SWIM)
|
||
&& todo != K_STEAL && todo != K_SPY && todo != K_SABOTAGE)
|
||
continue;
|
||
|
||
switch (todo) {
|
||
case K_ENTERTAIN:
|
||
entertain_cmd(u, u->thisorder);
|
||
break;
|
||
|
||
case K_WORK:
|
||
if (!rule_autowork() && do_work(u, u->thisorder, nextworker) == 0) {
|
||
assert(nextworker - workers < MAX_WORKERS);
|
||
++nextworker;
|
||
}
|
||
break;
|
||
|
||
case K_TAX:
|
||
tax_cmd(u, u->thisorder, &taxorders);
|
||
break;
|
||
|
||
case K_LOOT:
|
||
loot_cmd(u, u->thisorder, &lootorders);
|
||
break;
|
||
|
||
case K_STEAL:
|
||
steal_cmd(u, u->thisorder, &stealorders);
|
||
break;
|
||
|
||
case K_SPY:
|
||
spy_cmd(u, u->thisorder);
|
||
break;
|
||
|
||
case K_SABOTAGE:
|
||
sabotage_cmd(u, u->thisorder);
|
||
break;
|
||
|
||
case K_BREED:
|
||
breed_cmd(u, u->thisorder);
|
||
break;
|
||
|
||
case K_RESEARCH:
|
||
research_cmd(u, u->thisorder);
|
||
break;
|
||
default:
|
||
/* not handled here */
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Entertainment (expandentertainment) und Besteuerung (expandtax) vor den
|
||
* Befehlen, die den Bauern mehr Geld geben, damit man aus den Zahlen der
|
||
* letzten Runde berechnen kann, wieviel die Bauern f<>r Unterhaltung
|
||
* auszugeben bereit sind. */
|
||
if (entertaining)
|
||
expandentertainment(r);
|
||
if (!rule_autowork()) {
|
||
expandwork(r, workers, nextworker, maxworkingpeasants(r));
|
||
}
|
||
if (taxorders)
|
||
expandtax(r, taxorders);
|
||
|
||
if (lootorders)
|
||
expandloot(r, lootorders);
|
||
|
||
/* An erster Stelle Kaufen (expandbuying), die Bauern so Geld bekommen, um
|
||
* nachher zu beim Verkaufen (expandselling) den Spielern abkaufen zu
|
||
* k<>nnen. */
|
||
|
||
if (buyorders)
|
||
expandbuying(r, buyorders);
|
||
|
||
if (sellorders) {
|
||
int limit = rpeasants(r) / TRADE_FRACTION;
|
||
if (r->terrain == newterrain(T_DESERT)
|
||
&& buildingtype_exists(r, caravan_bt, true))
|
||
limit *= 2;
|
||
expandselling(r, sellorders, limited ? limit : INT_MAX);
|
||
}
|
||
|
||
/* Die Spieler sollen alles Geld verdienen, bevor sie beklaut werden
|
||
* (expandstealing). */
|
||
|
||
if (stealorders)
|
||
expandstealing(r, stealorders);
|
||
|
||
assert(rmoney(r) >= 0);
|
||
assert(rpeasants(r) >= 0);
|
||
}
|